diff --git a/__tests__/shared/components/Contentful/SearchBar/SearchBar.jsx b/__tests__/shared/components/Contentful/SearchBar/SearchBar.jsx deleted file mode 100644 index f4c032931f..0000000000 --- a/__tests__/shared/components/Contentful/SearchBar/SearchBar.jsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import Renderer from 'react-test-renderer/shallow'; -import { SearchBarInner } from 'components/Contentful/SearchBar/SearchBar'; - -test('Matches shallow shapshot', () => { - const renderer = new Renderer(); - const MOCK_PROPS = { - selected: '', - options: [], - theme: {}, - }; - renderer.render(); - expect(renderer.getRenderOutput()).toMatchSnapshot(); -}); diff --git a/__tests__/shared/components/Contentful/SearchBar/__snapshots__/SearchBar.jsx.snap b/__tests__/shared/components/Contentful/SearchBar/__snapshots__/SearchBar.jsx.snap deleted file mode 100644 index 1772d9ce45..0000000000 --- a/__tests__/shared/components/Contentful/SearchBar/__snapshots__/SearchBar.jsx.snap +++ /dev/null @@ -1,53 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Matches shallow shapshot 1`] = ` -
-
- - -
- -
-
-
-`; diff --git a/__tests__/shared/components/Contentful/SearchPageFilter/FilterAuthor/FilterAuthor.jsx b/__tests__/shared/components/Contentful/SearchPageFilter/FilterAuthor/FilterAuthor.jsx deleted file mode 100644 index 5c40cfd351..0000000000 --- a/__tests__/shared/components/Contentful/SearchPageFilter/FilterAuthor/FilterAuthor.jsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import Renderer from 'react-test-renderer/shallow'; -import { FilterAuthorInner } from 'components/Contentful/SearchPageFilter/FilterAuthor'; - -test('Matches shallow shapshot', () => { - const renderer = new Renderer(); - const MOCK_PROPS = { - theme: {}, - selected: 'All authors', - }; - renderer.render(); - expect(renderer.getRenderOutput()).toMatchSnapshot(); -}); diff --git a/__tests__/shared/components/Contentful/SearchPageFilter/FilterAuthor/__snapshots__/FilterAuthor.jsx.snap b/__tests__/shared/components/Contentful/SearchPageFilter/FilterAuthor/__snapshots__/FilterAuthor.jsx.snap deleted file mode 100644 index e8c4b0a310..0000000000 --- a/__tests__/shared/components/Contentful/SearchPageFilter/FilterAuthor/__snapshots__/FilterAuthor.jsx.snap +++ /dev/null @@ -1,25 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Matches shallow shapshot 1`] = ` -
- - Author - - -
-`; diff --git a/__tests__/shared/components/Contentful/SearchPageFilter/FilterDate/FilterDate.jsx b/__tests__/shared/components/Contentful/SearchPageFilter/FilterDate/FilterDate.jsx deleted file mode 100644 index 37c02c2333..0000000000 --- a/__tests__/shared/components/Contentful/SearchPageFilter/FilterDate/FilterDate.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import Renderer from 'react-test-renderer/shallow'; -import { FilterDateInner } from 'components/Contentful/SearchPageFilter/FilterDate'; - -test('Matches shallow shapshot', () => { - const renderer = new Renderer(); - const MOCK_PROPS = { - theme: {}, - }; - renderer.render(); - expect(renderer.getRenderOutput()).toMatchSnapshot(); -}); diff --git a/__tests__/shared/components/Contentful/SearchPageFilter/FilterDate/__snapshots__/FilterDate.jsx.snap b/__tests__/shared/components/Contentful/SearchPageFilter/FilterDate/__snapshots__/FilterDate.jsx.snap deleted file mode 100644 index caf83b4709..0000000000 --- a/__tests__/shared/components/Contentful/SearchPageFilter/FilterDate/__snapshots__/FilterDate.jsx.snap +++ /dev/null @@ -1,38 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Matches shallow shapshot 1`] = ` -
- - From - - - - - - - - -
-`; diff --git a/__tests__/shared/components/Contentful/SearchPageFilter/FilterRadio/FilterRadio.jsx b/__tests__/shared/components/Contentful/SearchPageFilter/FilterRadio/FilterRadio.jsx deleted file mode 100644 index 9985ed1b5d..0000000000 --- a/__tests__/shared/components/Contentful/SearchPageFilter/FilterRadio/FilterRadio.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import Renderer from 'react-test-renderer/shallow'; -import { FilterRadioInner } from 'components/Contentful/SearchPageFilter/FilterRadio'; - -test('Matches shallow shapshot', () => { - const renderer = new Renderer(); - const MOCK_PROPS = { - theme: {}, - }; - renderer.render(); - expect(renderer.getRenderOutput()).toMatchSnapshot(); -}); diff --git a/__tests__/shared/components/Contentful/SearchPageFilter/FilterRadio/__snapshots__/FilterRadio.jsx.snap b/__tests__/shared/components/Contentful/SearchPageFilter/FilterRadio/__snapshots__/FilterRadio.jsx.snap deleted file mode 100644 index 2f9f770d7b..0000000000 --- a/__tests__/shared/components/Contentful/SearchPageFilter/FilterRadio/__snapshots__/FilterRadio.jsx.snap +++ /dev/null @@ -1,7 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Matches shallow shapshot 1`] = ` -
-`; diff --git a/__tests__/shared/components/Contentful/SearchPageFilter/FilterSelection/FilterSelection.jsx b/__tests__/shared/components/Contentful/SearchPageFilter/FilterSelection/FilterSelection.jsx deleted file mode 100644 index 9fa14b0fc4..0000000000 --- a/__tests__/shared/components/Contentful/SearchPageFilter/FilterSelection/FilterSelection.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import Renderer from 'react-test-renderer/shallow'; -import { FilterSelectionInner } from 'components/Contentful/SearchPageFilter/FilterSelection'; - -test('Matches shallow shapshot', () => { - const renderer = new Renderer(); - const MOCK_PROPS = { - theme: {}, - }; - renderer.render(); - expect(renderer.getRenderOutput()).toMatchSnapshot(); -}); diff --git a/__tests__/shared/components/Contentful/SearchPageFilter/FilterSelection/__snapshots__/FilterSelection.jsx.snap b/__tests__/shared/components/Contentful/SearchPageFilter/FilterSelection/__snapshots__/FilterSelection.jsx.snap deleted file mode 100644 index 2f9f770d7b..0000000000 --- a/__tests__/shared/components/Contentful/SearchPageFilter/FilterSelection/__snapshots__/FilterSelection.jsx.snap +++ /dev/null @@ -1,7 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Matches shallow shapshot 1`] = ` -
-`; diff --git a/__tests__/shared/components/Contentful/SearchPageFilter/FilterTags/FilterTags.jsx b/__tests__/shared/components/Contentful/SearchPageFilter/FilterTags/FilterTags.jsx deleted file mode 100644 index a6984ff231..0000000000 --- a/__tests__/shared/components/Contentful/SearchPageFilter/FilterTags/FilterTags.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import Renderer from 'react-test-renderer/shallow'; -import { FilterTagsInner } from 'components/Contentful/SearchPageFilter/FilterTags'; - -test('Matches shallow shapshot', () => { - const renderer = new Renderer(); - const MOCK_PROPS = { - theme: {}, - }; - renderer.render(); - expect(renderer.getRenderOutput()).toMatchSnapshot(); -}); diff --git a/__tests__/shared/components/Contentful/SearchPageFilter/FilterTags/__snapshots__/FilterTags.jsx.snap b/__tests__/shared/components/Contentful/SearchPageFilter/FilterTags/__snapshots__/FilterTags.jsx.snap deleted file mode 100644 index 511ea5807c..0000000000 --- a/__tests__/shared/components/Contentful/SearchPageFilter/FilterTags/__snapshots__/FilterTags.jsx.snap +++ /dev/null @@ -1,15 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Matches shallow shapshot 1`] = ` -
- -
-
-`; diff --git a/__tests__/shared/components/Contentful/SearchPageFilter/SearchPageFilter.jsx b/__tests__/shared/components/Contentful/SearchPageFilter/SearchPageFilter.jsx deleted file mode 100644 index 54b8282bac..0000000000 --- a/__tests__/shared/components/Contentful/SearchPageFilter/SearchPageFilter.jsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import Renderer from 'react-test-renderer/shallow'; -import { SearchPageFilterInner } from 'components/Contentful/SearchPageFilter/SearchPageFilter'; - -test('Matches shallow shapshot', () => { - const renderer = new Renderer(); - const MOCK_PROPS = { - theme: {}, - startDate: '2019-08-01T07:21:20.249Z', - endDate: '2019-09-01T07:21:20.249Z', - }; - renderer.render(); - expect(renderer.getRenderOutput()).toMatchSnapshot(); -}); diff --git a/__tests__/shared/components/Contentful/SearchPageFilter/__snapshots__/SearchPageFilter.jsx.snap b/__tests__/shared/components/Contentful/SearchPageFilter/__snapshots__/SearchPageFilter.jsx.snap deleted file mode 100644 index e72483934a..0000000000 --- a/__tests__/shared/components/Contentful/SearchPageFilter/__snapshots__/SearchPageFilter.jsx.snap +++ /dev/null @@ -1,94 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Matches shallow shapshot 1`] = ` -
-
- - filter - - -
-
-
- - category - -
- -
-
-
- - filter - -
- - - -
-
- -
-
-`; diff --git a/__tests__/shared/components/Contentful/TracksFilter/TracksAuthor/TracksAuthor.jsx b/__tests__/shared/components/Contentful/TracksFilter/TracksAuthor/TracksAuthor.jsx deleted file mode 100644 index 8a3183493d..0000000000 --- a/__tests__/shared/components/Contentful/TracksFilter/TracksAuthor/TracksAuthor.jsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import Renderer from 'react-test-renderer/shallow'; -import { TracksAuthorInner } from 'components/Contentful/TracksFilter/TracksAuthor'; - -test('Matches shallow shapshot', () => { - const renderer = new Renderer(); - const MOCK_PROPS = { - theme: {}, - selected: 'All authors', - }; - renderer.render(); - expect(renderer.getRenderOutput()).toMatchSnapshot(); -}); diff --git a/__tests__/shared/components/Contentful/TracksFilter/TracksAuthor/__snapshots__/TracksAuthor.jsx.snap b/__tests__/shared/components/Contentful/TracksFilter/TracksAuthor/__snapshots__/TracksAuthor.jsx.snap deleted file mode 100644 index e8c4b0a310..0000000000 --- a/__tests__/shared/components/Contentful/TracksFilter/TracksAuthor/__snapshots__/TracksAuthor.jsx.snap +++ /dev/null @@ -1,25 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Matches shallow shapshot 1`] = ` -
- - Author - - -
-`; diff --git a/__tests__/shared/components/Contentful/TracksFilter/TracksDate/TracksDate.jsx b/__tests__/shared/components/Contentful/TracksFilter/TracksDate/TracksDate.jsx deleted file mode 100644 index 08f91606aa..0000000000 --- a/__tests__/shared/components/Contentful/TracksFilter/TracksDate/TracksDate.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import Renderer from 'react-test-renderer/shallow'; -import { TracksDateInner } from 'components/Contentful/TracksFilter/TracksDate'; - -test('Matches shallow shapshot', () => { - const renderer = new Renderer(); - const MOCK_PROPS = { - theme: {}, - }; - renderer.render(); - expect(renderer.getRenderOutput()).toMatchSnapshot(); -}); diff --git a/__tests__/shared/components/Contentful/TracksFilter/TracksDate/__snapshots__/TracksDate.jsx.snap b/__tests__/shared/components/Contentful/TracksFilter/TracksDate/__snapshots__/TracksDate.jsx.snap deleted file mode 100644 index 0978c0bdd8..0000000000 --- a/__tests__/shared/components/Contentful/TracksFilter/TracksDate/__snapshots__/TracksDate.jsx.snap +++ /dev/null @@ -1,44 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Matches shallow shapshot 1`] = ` -
- - Date - - - - - - - - - -
-`; diff --git a/__tests__/shared/components/Contentful/TracksFilter/TracksFilter.jsx b/__tests__/shared/components/Contentful/TracksFilter/TracksFilter.jsx deleted file mode 100644 index 6e7b09b0ae..0000000000 --- a/__tests__/shared/components/Contentful/TracksFilter/TracksFilter.jsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import Renderer from 'react-test-renderer/shallow'; -import { TracksFilterInner } from 'components/Contentful/TracksFilter/TracksFilter'; - -test('Matches shallow shapshot', () => { - const renderer = new Renderer(); - const MOCK_PROPS = { - theme: {}, - startDate: '2019-08-01T07:21:20.249Z', - endDate: '2019-09-01T07:21:20.249Z', - }; - renderer.render(); - expect(renderer.getRenderOutput()).toMatchSnapshot(); -}); diff --git a/__tests__/shared/components/Contentful/TracksFilter/TracksTags/TracksTags.jsx b/__tests__/shared/components/Contentful/TracksFilter/TracksTags/TracksTags.jsx deleted file mode 100644 index 328e5ade34..0000000000 --- a/__tests__/shared/components/Contentful/TracksFilter/TracksTags/TracksTags.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import Renderer from 'react-test-renderer/shallow'; -import { TracksTagsInner } from 'components/Contentful/TracksFilter/TracksTags'; - -test('Matches shallow shapshot', () => { - const renderer = new Renderer(); - const MOCK_PROPS = { - theme: {}, - }; - renderer.render(); - expect(renderer.getRenderOutput()).toMatchSnapshot(); -}); diff --git a/__tests__/shared/components/Contentful/TracksFilter/TracksTags/__snapshots__/TracksTags.jsx.snap b/__tests__/shared/components/Contentful/TracksFilter/TracksTags/__snapshots__/TracksTags.jsx.snap deleted file mode 100644 index 62182b0ff3..0000000000 --- a/__tests__/shared/components/Contentful/TracksFilter/TracksTags/__snapshots__/TracksTags.jsx.snap +++ /dev/null @@ -1,19 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Matches shallow shapshot 1`] = ` -
- - Tags - -
- -
-
-`; diff --git a/__tests__/shared/components/Contentful/TracksFilter/__snapshots__/TracksFilter.jsx.snap b/__tests__/shared/components/Contentful/TracksFilter/__snapshots__/TracksFilter.jsx.snap deleted file mode 100644 index a16029d305..0000000000 --- a/__tests__/shared/components/Contentful/TracksFilter/__snapshots__/TracksFilter.jsx.snap +++ /dev/null @@ -1,74 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Matches shallow shapshot 1`] = ` -
-
- - filter - - -
- -
- - -
-
- - -
-
-`; diff --git a/__tests__/shared/components/__snapshots__/TopcoderFooter.jsx.snap b/__tests__/shared/components/__snapshots__/TopcoderFooter.jsx.snap index 6b3d2bb144..10310b5e75 100644 --- a/__tests__/shared/components/__snapshots__/TopcoderFooter.jsx.snap +++ b/__tests__/shared/components/__snapshots__/TopcoderFooter.jsx.snap @@ -303,6 +303,15 @@ exports[`Matches shallow shapshot 1`] = ` About Community +
  • + + Changelog + +
  • diff --git a/config/default.js b/config/default.js index 65aa340f6a..270f233b68 100644 --- a/config/default.js +++ b/config/default.js @@ -182,6 +182,8 @@ module.exports = { * parameters that should never be send to the client side. */ SECRET: { CONTENTFUL: { + DEFAULT_SPACE_NAME: 'default', + DEFAULT_ENVIRONMENT: 'master', MANAGEMENT_TOKEN: '', // Personal Access Token to use the Content Management API default: { // Human-readable name of space SPACE_ID: '', diff --git a/package-lock.json b/package-lock.json index f0a3a85c73..286c66f0f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1554,9 +1554,9 @@ } }, "@types/express-serve-static-core": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.4.tgz", - "integrity": "sha512-dPs6CaRWxsfHbYDVU51VjEJaUJEcli4UI0fFMT4oWmgCvHj+j7oIxz5MLHVL0Rv++N004c21ylJNdWQvPkkb5w==", + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.5.tgz", + "integrity": "sha512-578YH5Lt88AKoADy0b2jQGwJtrBxezXtVe/MBqWXKZpqx91SnC0pVkVCcxcytz3lWW+cHBYDi3Ysh0WXc+rAYw==", "requires": { "@types/node": "*", "@types/range-parser": "*" @@ -2121,13 +2121,6 @@ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "requires": { "sprintf-js": "~1.0.2" - }, - "dependencies": { - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" - } } }, "aria-query": { @@ -4156,15 +4149,15 @@ } }, "caniuse-db": { - "version": "1.0.30001040", - "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30001040.tgz", - "integrity": "sha512-rrwOYbMn4sTFNdqT4sD1gtrfOKr8CPyUEw3FMhlEYb4b+KHhBJY+a5NROsNTBDoABVHyW6v1EKG1iuJx+gosAg==", + "version": "1.0.30001042", + "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30001042.tgz", + "integrity": "sha512-2RKrB2hkLCW/8Uj32oaXj0O+N9ROo0/BF0EueWHwgs6AeeSiL+rCSsbICR3ayBJOZavgcFx65ZCw7QiafsoUFQ==", "dev": true }, "caniuse-lite": { - "version": "1.0.30001040", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001040.tgz", - "integrity": "sha512-Ep0tEPeI5wCvmJNrXjE3etgfI+lkl1fTDU6Y3ZH1mhrjkPlVI9W4pcKbMo+BQLpEWKVYYp2EmYaRsqpPC3k7lQ==", + "version": "1.0.30001042", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001042.tgz", + "integrity": "sha512-igMQ4dlqnf4tWv0xjaaE02op9AJ2oQzXKjWf4EuAHFN694Uo9/EfPVIPJcmn2WkU9RqozCxx5e2KPcVClHDbDw==", "dev": true }, "capture-exit": { @@ -4381,9 +4374,9 @@ } }, "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", "dev": true }, "clipboard": { @@ -4949,16 +4942,36 @@ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" }, + "contentful": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/contentful/-/contentful-7.14.2.tgz", + "integrity": "sha512-g4CQuQUMBlvx7igaAYVZ6jyMl4C1pcKNa2gu86XapaJSI0eG+0V6YfkMPc/gaTxfIj4055YAs+kB0sPRNZ/hrQ==", + "requires": { + "axios": "^0.19.1", + "contentful-resolve-response": "^1.1.4", + "contentful-sdk-core": "^6.4.0", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.11" + } + }, "contentful-management": { - "version": "5.21.1", - "resolved": "https://registry.npmjs.org/contentful-management/-/contentful-management-5.21.1.tgz", - "integrity": "sha512-82Ly4a6lT9k8+JBl+g9BcEYH9Krsnw3FzKayR2hhurERLdq8Gs2nQ1MoartIeVE2M5bXHNu+R97bx70emWAf+w==", + "version": "5.23.0", + "resolved": "https://registry.npmjs.org/contentful-management/-/contentful-management-5.23.0.tgz", + "integrity": "sha512-N0RoJYLauJoo6g3ThygN8l/OVHR0Np7a30+nkNPtkPew/tjFbNqhe9RP+ZLz91veU6OWZXG51ourvsfcsxUPxA==", "requires": { "axios": "^0.19.0", "contentful-sdk-core": "^6.4.0", "lodash": "^4.17.11" } }, + "contentful-resolve-response": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/contentful-resolve-response/-/contentful-resolve-response-1.1.4.tgz", + "integrity": "sha512-oFq6n6zjbiwD9/7mBa8YHPwvPM0B0D4uOgg1n/rVzpQPhCrzeIixNj6fbJAbDiJt05rZqxiY3K1Db7pPRhRaZw==", + "requires": { + "lodash": "^4.17.4" + } + }, "contentful-sdk-core": { "version": "6.4.4", "resolved": "https://registry.npmjs.org/contentful-sdk-core/-/contentful-sdk-core-6.4.4.tgz", @@ -5342,6 +5355,12 @@ "requires": { "find-up": "^2.1.0" } + }, + "serialize-javascript": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.9.1.tgz", + "integrity": "sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==", + "dev": true } } }, @@ -5351,9 +5370,9 @@ "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" }, "core-js-pure": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.4.tgz", - "integrity": "sha512-epIhRLkXdgv32xIUFaaAry2wdxZYBi6bgM7cB136dzzXXa+dFyRLTZeLUJxnd8ShrmyVXBub63n2NHo2JAt8Cw==", + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.5.tgz", + "integrity": "sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==", "dev": true }, "core-util-is": { @@ -5361,6 +5380,15 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, "cosmiconfig": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", @@ -6599,9 +6627,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "electron-to-chromium": { - "version": "1.3.402", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.402.tgz", - "integrity": "sha512-gaCDfX7IUH0s3JmBiHCDPrvVcdnTTP1r4WLJc2dHkYYbLmXZ2XHiJCcGQ9Balf91aKTvuCKCyu2JjJYRykoI1w==", + "version": "1.3.412", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.412.tgz", + "integrity": "sha512-4bVdSeJScR8fT7ERveLWbxemY5uXEHVseqMRyORosiKcTUSGtVwBkV8uLjXCqoFLeImA57Z9hbz3TOid01U4Hw==", "dev": true }, "elliptic": { @@ -7232,18 +7260,18 @@ "integrity": "sha1-MKlHMDxrjV6VW+4rmbHSMyBqaQE=" }, "esquery": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.2.0.tgz", - "integrity": "sha512-weltsSqdeWIX9G2qQZz7KlTRJdkkOCTPgLYJUz1Hacf48R4YOwGPHO3+ORfWedqJKbq5WQmsgK90n+pFLIKt/Q==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", + "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", "dev": true, "requires": { - "estraverse": "^5.0.0" + "estraverse": "^5.1.0" }, "dependencies": { "estraverse": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.0.0.tgz", - "integrity": "sha512-j3acdrMzqrxmJTNj5dbr1YbjacrYgAxVMeF0gK16E3j494mOe7xygM/ZLIguEQ0ETwAg2hlJCtHRGav+y0Ny5A==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz", + "integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==", "dev": true } } @@ -9788,12 +9816,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true } } }, @@ -10138,9 +10160,9 @@ } }, "html-entities": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", - "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.3.1.tgz", + "integrity": "sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA==", "dev": true }, "html-tags": { @@ -10150,15 +10172,14 @@ "dev": true }, "html-to-text": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-3.3.0.tgz", - "integrity": "sha1-aptjxpm4hbt7qEsURr/mh2u/z7c=", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-5.1.1.tgz", + "integrity": "sha512-Bci6bD/JIfZSvG4s0gW/9mMKwBRoe/1RWLxUME/d6WUSZCdY7T60bssf/jFf7EYXRyqU4P5xdClVqiYU0/ypdA==", "requires": { - "he": "^1.0.0", - "htmlparser2": "^3.9.2", - "optimist": "^0.6.1", - "underscore": "^1.8.3", - "underscore.string": "^3.2.3" + "he": "^1.2.0", + "htmlparser2": "^3.10.1", + "lodash": "^4.17.11", + "minimist": "^1.2.0" } }, "htmlparser2": { @@ -12614,12 +12635,6 @@ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==", "dev": true - }, - "underscore": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", - "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", - "dev": true } } }, @@ -12933,17 +12948,17 @@ } }, "jwks-rsa": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-1.7.0.tgz", - "integrity": "sha512-tq7DVJt9J6wTvl9+AQfwZIiPSuY2Vf0F+MovfRTFuBqLB1xgDVhegD33ChEAQ6yBv9zFvUIyj4aiwrSA5VehUw==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-1.8.0.tgz", + "integrity": "sha512-+HYROHD5fsYQCNrJ37RSr2NjbN2/V9YT+yVF3oJxLmPIZWrmp1SOl1hMw2RcuNh+LGA2bGZIhRKGiMjhQa/b7Q==", "requires": { "@types/express-jwt": "0.0.42", + "axios": "^0.19.2", "debug": "^4.1.0", "jsonwebtoken": "^8.5.1", "limiter": "^1.1.4", "lru-memoizer": "^2.0.1", - "ms": "^2.1.2", - "request": "^2.88.0" + "ms": "^2.1.2" }, "dependencies": { "debug": { @@ -14226,7 +14241,7 @@ "dev": true }, "navigation-component": { - "version": "git+https://github.com/topcoder-platform/navigation-component.git#832e036d30460453aaf6241acfcd94a640d142aa", + "version": "git+https://github.com/topcoder-platform/navigation-component.git#24bd50897313b8236a2f0193de0ab5b7a26ab31b", "from": "git+https://github.com/topcoder-platform/navigation-component.git#develop", "requires": { "classnames": "^2.2.6", @@ -14283,6 +14298,11 @@ "symbol-observable": "^1.2.0" } }, + "serialize-javascript": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.9.1.tgz", + "integrity": "sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==" + }, "topcoder-react-utils": { "version": "0.7.9", "resolved": "https://registry.npmjs.org/topcoder-react-utils/-/topcoder-react-utils-0.7.9.tgz", @@ -14333,9 +14353,9 @@ "optional": true }, "nearley": { - "version": "2.19.1", - "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.19.1.tgz", - "integrity": "sha512-xq47GIUGXxU9vQg7g/y1o1xuKnkO7ev4nRWqftmQrLkfnE/FjRqDaGOUakM8XHPn/6pW3bGjU2wgoJyId90rqg==", + "version": "2.19.2", + "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.19.2.tgz", + "integrity": "sha512-h6lygT0BWAGErDvoE2LfI+tDeY2+UUrqG5dcBPdCmjnjud9z1wE0P7ljb85iNbE93YA+xJLpoSYGMuUqhnSSSA==", "dev": true, "requires": { "commander": "^2.19.0", @@ -14919,9 +14939,13 @@ "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" }, "object-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.2.tgz", - "integrity": "sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz", + "integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } }, "object-keys": { "version": "1.1.1", @@ -15063,22 +15087,6 @@ "resolved": "https://registry.npmjs.org/opentracing/-/opentracing-0.14.1.tgz", "integrity": "sha1-QNJ4vupBdmCjXdnT7nZRH/qRHc0=" }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - }, - "dependencies": { - "minimist": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" - } - } - }, "optimize-css-assets-webpack-plugin": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-4.0.3.tgz", @@ -17379,9 +17387,9 @@ "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==" }, "query-string": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.12.0.tgz", - "integrity": "sha512-aoiFW9ZU7jP8Itjqfpw80Qe7RoyCIhFrW522sdsp9LG92pat6CCG3d8qNZBaUi71FsEjIfLjx9Ky347FtVoqXA==", + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.12.1.tgz", + "integrity": "sha512-OHj+zzfRMyj3rmo/6G8a5Ifvw3AleL/EbcHMD27YA31Q+cO5lfmQxECkImuNVjcskLcvBRVHNAB3w6udMs1eAA==", "requires": { "decode-uri-component": "^0.2.0", "split-on-first": "^1.0.0", @@ -17944,9 +17952,9 @@ } }, "react-resize-detector": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-4.2.1.tgz", - "integrity": "sha512-ZfPMBPxXi0o3xox42MIEtz84tPSVMW9GgwLHYvjVXlFM+OkNzbeEtpVSV+mSTJmk4Znwomolzt35zHN9LNBQMQ==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-4.2.3.tgz", + "integrity": "sha512-4AeS6lxdz2KOgDZaOVt1duoDHrbYwSrUX32KeM9j6t9ISyRphoJbTRCMS1aPFxZHFqcCGLT1gMl3lEcSWZNW0A==", "requires": { "lodash": "^4.17.15", "lodash-es": "^4.17.15", @@ -19162,9 +19170,9 @@ "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" }, "resolve": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", - "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.16.0.tgz", + "integrity": "sha512-LarL/PIKJvc09k1jaeT4kQb/8/7P+qV4qSnN2K80AES+OHdfZELAKVOBjxsvtToT/uLOfFbvYvKfZmV8cee7nA==", "requires": { "path-parse": "^1.0.6" } @@ -20045,9 +20053,9 @@ "dev": true }, "serialize-javascript": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.9.1.tgz", - "integrity": "sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", + "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==" }, "serve-favicon": { "version": "2.5.0", @@ -20560,9 +20568,9 @@ } }, "sprintf-js": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", - "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "sshpk": { "version": "1.16.1", @@ -21979,7 +21987,7 @@ } }, "tc-accounts": { - "version": "git+https://github.com/appirio-tech/accounts-app.git#41529eab164d442cff0d43eeed07998c097a2afe", + "version": "git+https://github.com/appirio-tech/accounts-app.git#c30db73cddbf329ea502a42cf42a5fee2dfe5406", "from": "git+https://github.com/appirio-tech/accounts-app.git#dev", "requires": { "@uirouter/angularjs": "^1.0.0", @@ -32307,12 +32315,6 @@ "ajv-keywords": "^3.1.0" } }, - "serialize-javascript": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", - "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==", - "dev": true - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -32756,6 +32758,11 @@ "react-is": "^16.8.2" } }, + "serialize-javascript": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.9.1.tgz", + "integrity": "sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==" + }, "tc-core-library-js": { "version": "github:appirio-tech/tc-core-library-js#d16413db30b1eed21c0cf426e185bedb2329ddab", "from": "github:appirio-tech/tc-core-library-js#v2.6", @@ -32927,6 +32934,13 @@ "serve-favicon": "^2.5.0", "shortid": "^2.2.8", "url-parse": "^1.4.1" + }, + "dependencies": { + "serialize-javascript": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.9.1.tgz", + "integrity": "sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==" + } } }, "topo": { @@ -33075,38 +33089,20 @@ "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" }, "uglify-js": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.8.1.tgz", - "integrity": "sha512-W7KxyzeaQmZvUFbGj4+YFshhVrMBGSg2IbcYAjGWGvx8DHvJMclbTDMpffdxFUGPBHjIytk7KJUR/KUXstUGDw==", + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.9.1.tgz", + "integrity": "sha512-JUPoL1jHsc9fOjVFHdQIhqEEJsQvfKDjlubcCilu8U26uZ73qOg8VsN8O1jbuei44ZPlwL7kmbAdM4tzaUvqnA==", "dev": true, "optional": true, "requires": { - "commander": "~2.20.3", - "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - } + "commander": "~2.20.3" } }, "underscore": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz", - "integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg==" - }, - "underscore.string": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.5.tgz", - "integrity": "sha512-g+dpmgn+XBneLmXXo+sGlW5xQEt4ErkS3mgeN2GFbremYeMBSJKr9Wf2KJplQVaiPY/f7FN6atosWYNm9ovrYg==", - "requires": { - "sprintf-js": "^1.0.3", - "util-deprecate": "^1.0.2" - } + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", + "dev": true }, "unfetch": { "version": "4.1.0", @@ -34645,9 +34641,10 @@ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" }, "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true }, "wordwrapjs": { "version": "3.0.0", diff --git a/package.json b/package.json index 7a67a068a6..1269e5aa62 100644 --- a/package.json +++ b/package.json @@ -45,8 +45,10 @@ "browser-cookies": "^1.2.0", "classnames": "^2.2.6", "config": "^1.30.0", + "contentful": "^7.14.2", "contentful-management": "^5.10.0", "cookie-parser": "^1.4.3", + "cors": "^2.8.5", "country-list": "^2.1.1", "d3": "3.5.17", "dd-trace": "^0.7.1", diff --git a/src/server/index.js b/src/server/index.js index 25e4c3bd80..062cb1449c 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -26,7 +26,6 @@ import { toJson as xmlToJson } from 'utils/xml2json'; import cdnRouter from './routes/cdn'; import mailChimpRouter from './routes/mailchimp'; import mockDocuSignFactory from './__mocks__/docu-sign-mock'; -// import { pollArticlesForThrive } from 'server/services/contentful'; /* Dome API for topcoder communities */ import tcCommunitiesDemoApi from './tc-communities'; @@ -34,10 +33,6 @@ import tcCommunitiesDemoApi from './tc-communities'; import webpackConfigFactory from '../../webpack.config'; /* eslint-enable */ -// DISABLED -// Init TC Blog -> THRIVE poll bridge -// pollArticlesForThrive(); - global.atob = atob; const CMS_BASE_URL = `https://app.contentful.com/spaces/${config.SECRET.CONTENTFUL.SPACE_ID}`; diff --git a/src/server/routes/contentful.js b/src/server/routes/contentful.js index 463310255d..638ff7bd02 100644 --- a/src/server/routes/contentful.js +++ b/src/server/routes/contentful.js @@ -2,7 +2,6 @@ * The routes that expose assets and content from Contentful CMS to the CDN. */ -import config from 'config'; import express from 'express'; import { @@ -13,16 +12,14 @@ import { articleVote, } from '../services/contentful'; -const routes = express.Router(); +const cors = require('cors'); -const LOCAL_MODE = Boolean(config.CONTENTFUL.LOCAL_MODE); +const routes = express.Router(); -/* Sets Access-Control-Allow-Origin header to avoid CORS error. - * TODO: Replace the wildcard value by an appropriate origin filtering. */ -routes.use((req, res, next) => { - res.set('Access-Control-Allow-Origin', '*'); - next(); -}); +// Enables CORS on those routes according config above +// ToDo configure CORS for set of our trusted domains +routes.use(cors()); +routes.options('*', cors()); /* Gets non-image asset file. */ routes.use( @@ -60,7 +57,7 @@ routes.use( routes.use('/:spaceName/:environment/preview/assets/:id', (req, res, next) => { const { environment, id, spaceName } = req.params; getService(spaceName, environment, true) - .getAsset(id, !LOCAL_MODE) + .getAsset(id) .then(res.send.bind(res), next); }); @@ -68,7 +65,7 @@ routes.use('/:spaceName/:environment/preview/assets/:id', (req, res, next) => { routes.use('/:spaceName/:environment/preview/assets', (req, res, next) => { const { environment, spaceName } = req.params; getService(spaceName, environment, true) - .queryAssets(req.query, !LOCAL_MODE) + .queryAssets(req.query) .then(res.send.bind(res), next); }); @@ -94,7 +91,7 @@ routes.use( (req, res, next) => { const { environment, id, spaceName } = req.params; getService(spaceName, environment, false) - .getAsset(id, !LOCAL_MODE) + .getAsset(id) .then(res.send.bind(res), next); }, ); @@ -103,7 +100,7 @@ routes.use( routes.use(':spaceName/:environment/published/assets', (req, res, next) => { const { environment, spaceName } = req.params; getService(spaceName, environment, false) - .queryAssets(req.query, !LOCAL_MODE) + .queryAssets(req.query) .then(res.send.bind(res), next); }); @@ -132,25 +129,4 @@ routes.use('/:spaceName/:environment/votes', (req, res, next) => { .then(res.send.bind(res), next); }); -/* Returns index of assets and content. */ -/* -routes.use('/index', async (req, res, next) => { - try { - res.set('Cache-Control', `max-age=${1000}`); - res.send(await getIndex()); - } catch (err) { next(err); } -}); -*/ - -/* Returns URL for the next sync of assets and content index with Contentful - * API. */ -/* -routes.use('/next-sync-url', async (req, res, next) => { - try { - res.set('Cache-Control', `max-age=${1000}`); - res.send(await getNextSyncUrl()); - } catch (err) { next(err); } -}); -*/ - export default routes; diff --git a/src/server/services/contentful.js b/src/server/services/contentful.js index 8dba5cf7e2..431ceda155 100644 --- a/src/server/services/contentful.js +++ b/src/server/services/contentful.js @@ -1,76 +1,35 @@ -/* eslint-disable no-param-reassign */ /** - * Server-side functions necessary for effective integration with Contentful - * CMS. + * Server-side functions necessary for effective integration + * with Contentful CMS */ import _ from 'lodash'; import config from 'config'; -import fetch from 'isomorphic-fetch'; -// import logger from 'utils/logger'; -// import moment from 'moment'; -import qs from 'qs'; +import { createClient } from 'contentful'; import { logger } from 'topcoder-react-lib'; +import { isomorphy } from 'topcoder-react-utils'; const contentful = require('contentful-management'); -const xml2json = require('xml2json'); /* Holds Contentful CDN URL. */ const CDN_URL = 'https://cdn.contentful.com/spaces'; -/* Holds the maximal index age [ms]. - * - * Set to 1 minute, which means ~100k API requests to Contentful from our dev - * and prod environments (preview API calls apart, but there should be not that - * many of them, as the circle of potential editors is edit, compared to that of - * the regular website visitors). - */ -// const INDEX_MAXAGE = 60 * 1000; - /* Holds Contentful Preview URL. */ const PREVIEW_URL = 'https://preview.contentful.com/spaces'; -/* Holds base URL of Community App CDN. */ -// const TC_CDN_URL = `${config.CDN.PUBLIC}/contentful`; - export const ASSETS_DOMAIN = 'assets.ctfassets.net'; export const IMAGES_DOMAIN = 'images.ctfassets.net'; -const MAX_FETCH_RETRIES = 5; - -/* GENERAL-PURPOSE CONTETNFUL API SERVICE. */ - /** - * Given an asset object, replaces its original file URL by URL leading to our - * own CloudFront CDN. - * - * BEWARE: - * - It mutates the argument asset. - * - * @param {Object} asset + * Generic logger for errors and warnings + * from Contentful API calls + * @param {String} level + * @param {String} data */ -/* -function mapAssetFileUrlToCdn(asset) { - let x = asset.fields.file.url.split('/'); - switch (x[2]) { - case ASSETS_DOMAIN: - x = `${TC_CDN_URL}/assets/${x[4]}/${x[5]}/${x[6]}`; - break; - case IMAGES_DOMAIN: - x = `${TC_CDN_URL}/images/${x[4]}/${x[5]}/${x[6]}`; - break; - default: throw new Error('Unexpected asset location'); +function logHandler(level, data) { + if (isomorphy.isDev) { + logger.log('Contentful logHandler', level, data); } - asset.fields.file.url = x; // eslint-disable-line no-param-reassign -} -*/ - -/** - * Creates a promise that resolves two second after its creation. - * @return {Promise} - */ -function oneSecondDelay() { - return new Promise(resolve => setTimeout(resolve, 1000)); } /** @@ -82,54 +41,32 @@ class ApiService { * Creates a new service instance. * @param {String} baseUrl The base API endpoint. * @param {String} key API key. + * @param {String} spaceId The space id. + * @param {Boolean} preview Use the preview API? */ - constructor(baseUrl, key) { - this.private = { baseUrl, key }; - } - - /** - * Gets data from the specified endpoing. - * @param {String} endpoint - * @param {Object} query Optional. URL query to append to the request. - * @return {Promise} - */ - async fetch(endpoint, query) { - // Contentful API rate limits, which are 78 requests within 1 second - // we await 14ms before each request to make sure we don't break this limitation - await new Promise(resolve => setTimeout(resolve, 14)); - // fire calls to Contentful API - let url = `${this.private.baseUrl}${endpoint}`; - if (query) url += `?${qs.stringify(query)}`; - let res; - for (let i = 0; i < MAX_FETCH_RETRIES; i += 1) { - /* The loop is here to retry async operation multiple times in case of - * failures due to violation of Contentful API rate limits, which are - * 78 requests within 1 second. Thus, it is a valid use of await inside - * loop. */ - /* eslint-disable no-await-in-loop */ - res = await fetch(url, { - headers: { Authorization: `Bearer ${this.private.key}` }, - }); - /* 429 = "Too Many Requests" */ - if (res.status !== 429) break; - await oneSecondDelay(); - /* eslint-enable no-await-in-loop */ - } - if (!res.ok) throw new Error(res.statusText); - return res.json(); + constructor(baseUrl, key, spaceId, preview) { + this.private = { + baseUrl, key, spaceId, preview, + }; + // client config + const clientConf = { + accessToken: key, + space: spaceId, + logHandler, + }; + if (preview) clientConf.host = 'preview.contentful.com'; + // create the client to work with + this.client = createClient(clientConf); } /** * Gets the specified asset. * @param {String} id Asset ID. - * @param {Boolean} mapFileUrlToCdn Optional. Pass in `true` to replace the - * actual file path by Community App CDN path. * @return {Promise} */ - async getAsset(id /* , mapFileUrlToCdn */) { - const res = await this.fetch(`/assets/${id}`); - // if (mapFileUrlToCdn) mapAssetFileUrlToCdn(res); - return res; + async getAsset(id) { + const res = await this.client.getAsset(id); + return res.stringifySafe ? JSON.parse(res.stringifySafe()) : res; } /** @@ -137,21 +74,19 @@ class ApiService { * @param {String} id Entry ID. * @return {Promise} */ - getEntry(id) { - return this.fetch(`/entries/${id}`); + async getEntry(id) { + const res = await this.client.getEntry(id); + return res.stringifySafe ? JSON.parse(res.stringifySafe()) : res; } /** * Queries assets. * @param {Object} query Optional. Query. - * @param {Boolean} mapFileUrlToCdn Optional. Pass in `true` to replace the - * actual file path by Community App CDN path. * @return {Promise} */ - async queryAssets(query /* , mapFileUrlToCdn */) { - const res = await this.fetch('/assets', query); - // if (mapFileUrlToCdn) res.items.forEach(x => mapAssetFileUrlToCdn(x)); - return res; + async queryAssets(query) { + const res = await this.client.getAssets(query); + return res.stringifySafe ? JSON.parse(res.stringifySafe()) : res; } /** @@ -159,8 +94,9 @@ class ApiService { * @param {Object} query Optional. Query for filtering / sorting of entries. * @return {Promise} */ - queryEntries(query) { - return this.fetch('/entries', query); + async queryEntries(query) { + const res = await this.client.getEntries(query); + return res.stringifySafe ? JSON.parse(res.stringifySafe()) : res; } } @@ -179,17 +115,21 @@ export function articleVote(body) { .then(environment => environment.getEntry(body.id)) .then((entry) => { if (!entry.fields.upvotes) { + // eslint-disable-next-line no-param-reassign entry.fields.upvotes = { 'en-US': body.votes.upvotes, }; } else { + // eslint-disable-next-line no-param-reassign entry.fields.upvotes['en-US'] = body.votes.upvotes; } if (!entry.fields.downvotes) { + // eslint-disable-next-line no-param-reassign entry.fields.downvotes = { 'en-US': body.votes.downvotes, }; } else { + // eslint-disable-next-line no-param-reassign entry.fields.downvotes['en-US'] = body.votes.downvotes; } return entry.update(); @@ -197,136 +137,12 @@ export function articleVote(body) { .then(entry => entry.publish()); } -/** - * This function fetches TC RSS feed to create draft articles in THRIVE for the new posted blogs. - * It calls itself by interval to poll for new blogs. - * Runs on server side only. - */ -export async function pollArticlesForThrive() { - // make this function execute only when in production - if (process.env.BABEL_ENV !== 'production') return; - logger.log('polling blog articles for THRIVE -> INIT'); - // Create Contentful client to work with - const client = contentful.createClient({ - accessToken: config.SECRET.CONTENTFUL.MANAGEMENT_TOKEN, - }); - // connect to Thrive space - const env = await client.getSpace(config.SECRET.CONTENTFUL.EDU.SPACE_ID) - .then(space => space.getEnvironment('master')); - // fetch the poll interval - const pollConf = await env.getEntry('7n1HT3MhUhgjvCzd36Nymd'); - const pollInt = pollConf ? pollConf.fields.timeInSeconds : 86400; // default to day if not found - // fetch the RSS feed and parse it - const feedXML = await fetch(config.URL.THRIVE_POLL_FEED); - if (feedXML.ok) { - let feed = await feedXML.text(); - feed = await Promise.resolve(xml2json.toJson(feed, { object: true })); - if (feed) { - // when feed loaded and parsed ok - // apply filter first based on tags work only with 'Community Stories' - feed.rss.channel.item = _.filter( - feed.rss.channel.item, - blogItem => blogItem.category.indexOf('Community Stories') !== -1, - ); - // loop all feed items and check if those exists in space - // if not existing then create - Promise.all( - _.map(feed.rss.channel.item, - blogPost => env.getEntries({ - content_type: 'article', - 'fields.title[match]': blogPost.title, - }) - .then((queryData) => { - if (queryData.total === 0) { - // article not found in Contentful space - // will create it with payload... - const article = { - fields: { - title: { 'en-US': blogPost.title }, - type: { 'en-US': 'Article' }, - tags: { 'en-US': blogPost.category }, - creationDate: { 'en-US': new Date(blogPost.pubDate) }, - content: { 'en-US': blogPost.description }, - externalArticle: { 'en-US': true }, - contentUrl: { 'en-US': blogPost.link }, - }, - }; - // check if author exists - // if yes link it if no create it? - return env.getEntries({ - content_type: 'person', - query: blogPost['dc:creator'], - }) - .then((author) => { - // found an author that matches link it - if (author.total) { - article.fields.contentAuthor = { - 'en-US': [{ - sys: { - type: 'Link', linkType: 'Entry', id: author.items[0].sys.id, - }, - }], - }; - } - // try get the page to extract its featured image - return fetch(blogPost.link) - .then(rsp => rsp.text()) - .then((HTMLsrc) => { - const match = /]*href="([^"]+)"/gm.exec(HTMLsrc); - if (match && match[1]) { - // create the asset in Contentful - return env.createAsset({ - fields: { - title: { 'en-US': blogPost.title }, - file: { - 'en-US': { fileName: blogPost.title, contentType: 'image', upload: match[1] }, - }, - }, - }) - .then(asset => asset.processForAllLocales()) - .then((asset) => { - article.fields.featuredImage = { - 'en-US': { - sys: { - type: 'Link', linkType: 'Asset', id: asset.sys.id, - }, - }, - }; - return env.createEntry('article', article); - }); - } - // could not find image - // just create the article without it... - return env.createEntry('article', article); - }); - }); - } - // Article exists in Contentful space - // do nothing... - return 1; - })), - ) - .then(() => logger.log('polling blog articles for THRIVE -> DONE')); - } - } - // schedule a repeat each TC_EDU_POLL_TIME - setTimeout( - pollArticlesForThrive, - pollInt * 1000 + (Math.floor(Math.random() * Math.floor(5)) * 60 * 1000), - ).unref(); -} - -// /* Contentful CDN service. */ -// export const cdnService = new ApiService(CDN_URL, CDN_KEY); - -// /* Contentful Preview service. */ -// export const previewService = new ApiService(PREVIEW_URL, PREVIEW_KEY); - - let services; function initServiceInstances() { - const contentfulConfig = config.SECRET.CONTENTFUL; + const contentfulConfig = _.omit(config.SECRET.CONTENTFUL, [ + 'DEFAULT_SPACE_NAME', 'DEFAULT_ENVIRONMENT', 'MANAGEMENT_TOKEN', + ]); services = {}; _.map(contentfulConfig, (spaceConfig, spaceName) => { services[spaceName] = {}; @@ -338,8 +154,10 @@ function initServiceInstances() { const cdnBaseUrl = `${CDN_URL}/${spaceId}/environments/${environment}`; const svcs = {}; - svcs.previewService = new ApiService(previewBaseUrl.toString(), env.PREVIEW_API_KEY); - svcs.cdnService = new ApiService(cdnBaseUrl.toString(), env.CDN_API_KEY); + svcs.previewService = new ApiService( + previewBaseUrl.toString(), env.PREVIEW_API_KEY, spaceId, true, + ); + svcs.cdnService = new ApiService(cdnBaseUrl.toString(), env.CDN_API_KEY, spaceId); services[spaceName][environment] = svcs; } }); @@ -378,302 +196,3 @@ export function getService(spaceName, environment, preview) { const service = services[name][env]; return preview ? service.previewService : service.cdnService; } - -/** - * Generates the last version for content index, and other similar data that - * have to be refreshed regularly to keep us in sync with content edits in CMS. - * @return {Number} - */ -/* -function getLastVersion() { - const now = Date.now(); - return now - (now % INDEX_MAXAGE); -} -*/ - -/** - * Gets the index of assets and entries via Community App CDN. - * @param {Number} version Optional. The version of index to fetch. Defaults to - * the latest index version. - * @return {Promise} - */ -/* -async function getIndexViaCdn(version = getLastVersion()) { - const res = await fetch(`${TC_CDN_URL}/index?version=${version}`); - if (!res.ok) { - const MSG = 'Failed to get the index'; - logger.error(MSG, res); - throw new Error(MSG); - } - return res.json(); -} -*/ - -/** - * Gets the next sync URL via CDN. - * @param {Number} version Optional. The version of index to fetch. Defaults to - * the latest version. - * @return {Promise} - */ -/* -async function getNextSyncUrlViaCdn(version = getLastVersion()) { - const res = - await fetch(`${TC_CDN_URL}/next-sync-url?version=${version}`); - if (!res.ok) { - const MSG = 'Failed to get the next sync URL'; - logger.error(MSG, res.statusText); - throw new Error(MSG); - } - return res.text(); -} -*/ - -/* THE INDEX OF CMS ASSETS AND ENTRIES. - * - * A tricky logic is involved to keep it all working properly, beware to modify - * and in all cases prefer to use exported functions that provide access to the - * index and take care about its correct updating. */ - -/* The barrier for syncronization of parallel calls to async functions exported - * by this module. If not null, then it is a promise that signals that the index - * update is in progress; in this case any function that needs to access the - * index should wait until the promise is resolved. */ -// let barrier = null; - -/* Holds the next sync URL provided by the CMS. */ -// let nextSyncUrl; - -/* The public index of CMS assets and entries. It is the map between CMS IDs of - * these assets/entries and the timestamps of their last updates. Note that this - * index is accessible by the frontend via CDN, thus anybody can access it and - * thus all assets and entries mentioned in this index. That's why announcements - * with future startDate are not included into this index. */ -// let publicIndex; - -/** - * Adds a new asset to the index, or updates the existing one. - * @param {Object} asset - */ -/* -function indexAsset(asset) { - const { id, createdAt, updatedAt } = asset.sys; - publicIndex.assets[id] = moment(updatedAt || createdAt).valueOf(); -} -*/ - -/** - * Adds a new entry to the index, or updates the existing one. - * @param {Object} entry - */ -/* -function indexEntry(entry) { - let isPublic = true; - const { id, createdAt, updatedAt } = entry.sys; - const timestamp = moment(updatedAt || createdAt).valueOf(); - - const type = entry.sys.contentType.sys.id; - switch (type) { - /* We use an additional index of dashboard announcement to be able to find - * out which announcement should be show at any moment, without a call to - * CMS. We also do not include future announcements into the public index - * to avoid any exposure to general public before the time. */ -/* - case 'dashboardAnnouncement': { - const now = Date.now(); - const endDate = moment(entry.fields.endDate['en-US']).valueOf(); - const startDate = moment(entry.fields.startDate['en-US']).valueOf(); - isPublic = now > startDate; - if (now < endDate) { - currentDashboardAnnouncementsMap[id] = { - id, - endDate, - startDate, - timestamp, - }; - } - break; - } - default: - } - - if (isPublic) publicIndex.entries[id] = timestamp; -} -*/ - -/** - * Adds a new asset or entry to the index, or updates / removes the existing - * one. - * @param {Object} item - */ -/* -function indexItem(item) { - const { id, type } = item.sys; - switch (type) { - case 'Asset': indexAsset(item); break; - case 'DeletedAsset': delete publicIndex.assets[id]; break; - case 'DeletedEntry': - delete currentDashboardAnnouncementsMap[id]; - delete publicIndex.entries[id]; - break; - case 'Entry': indexEntry(item); break; - default: throw new Error('Invariant violation'); - } -} -*/ - -/** - * Updates the current announcement ID. - */ -/* -function updateCurrentDashboardAnnouncementId() { - const list = []; - const now = Date.now(); - Object.values(currentDashboardAnnouncementsMap).forEach((item) => { - if (item.endDate < now) delete currentDashboardAnnouncementsMap[item.id]; - else if (item.startDate < now) list.push(item); - }); - if (list.length) { - list.sort((a, b) => b.startDate - a.startDate); - currentDashboardAnnouncementId = list[0].id; - } else currentDashboardAnnouncementId = ''; - if (currentDashboardAnnouncementId - && (!publicIndex.entries[currentDashboardAnnouncementId])) { - publicIndex.entries[currentDashboardAnnouncementId] = list[0].timestamp; - } -} -*/ - -/** - * Updates the index. - * @return {Promise} - */ -/* -async function updateIndex() { - let nextPageUrl = nextSyncUrl; - while (nextPageUrl) { - /* Disabled, as we really need to keep these iterations sequential, thus - * await inside the loop is not an error. */ -/* eslint-disable no-await-in-loop */ -/* - let d = await fetch(nextPageUrl, { - headers: { Authorization: `Bearer ${CDN_KEY}` }, - }); - if (!d.ok) { - const MSG = 'Failed to update the index'; - logger.error(MSG, d.statusText); - throw new Error(MSG); - } - d = await d.json(); - /* eslint-anable no-await-in-loop */ -/* - d.items.forEach(indexItem); - ({ nextPageUrl, nextSyncUrl } = d); - } - publicIndex.timestamp = Date.now(); - updateCurrentDashboardAnnouncementId(); -} -*/ - -/** - * Inits the index with data from CMS. - * @return {Promise} - */ -/* -async function initIndex() { - /* Gets necessary data from CMS. */ -/* - let d = await fetch(`${CDN_URL}/sync?initial=true`, { - headers: { Authorization: `Bearer ${CDN_KEY}` }, - }); - if (!d.ok) { - const MSG = 'Failed to initialize the index'; - logger.error(MSG, d.statusText); - throw new Error(MSG); - } - d = await d.json(); - - /* Generates the index. */ -/* - publicIndex = { - assets: {}, - entries: {}, - }; - currentDashboardAnnouncementsMap = {}; - d.items.forEach(indexItem); - publicIndex.timestamp = Date.now(); - updateCurrentDashboardAnnouncementId(); - - /* In case the initial update is too large to fit into a single response. - * TODO: This updateIndex(..) function can be combined with initIndex(..) - * into a single function. The URL query is the only real difference between - * them. */ -/* - if (d.nextPageUrl) { - nextSyncUrl = d.nextPageUrl; - await updateIndex(); - } else ({ nextSyncUrl } = d); -} -*/ - -/** - * Returns the index of CMS assets and content, along with the timestamps of - * their last updates. This function also takes care about initialization and - * automatic updates of the index, as necessary. - * @return {Promise} - */ -/* -export async function getIndex() { - while (barrier) await barrier; - if (!publicIndex) barrier = initIndex(); - else if (Date.now() - publicIndex.timestamp > INDEX_MAXAGE) { - barrier = updateIndex(); - } - if (barrier) { - await barrier; - barrier = null; - - /* These two calls are necessary to cache the updated index by CDN. */ -/* - getIndexViaCdn(); - getCurrentDashboardAnnouncementsIndexViaCdn(); - getNextSyncUrlViaCdn(); - } - - return publicIndex; -} -*/ - -/** - * Returns the next sync URL. - * @return {Promise} - */ -/* -export async function getNextSyncUrl() { - while (barrier) await barrier; - if (!publicIndex || Date.now() - publicIndex.timestamp > INDEX_MAXAGE) { - await getIndex(); - } - return nextSyncUrl; -} -*/ - -/* Module initialization. - * This code tries to pull the current index from CDN, where it is supposed to - * be cached, to keep it persistent across re-deployments of the app, and also - * to prevent unnecessary calls to Contentful APIs from a locally deployed - * server. In case of failure, it initializes the new index using getIndex() - * function directly. */ -/* -let version = Date.now() - INDEX_MAXAGE; -version -= version % INDEX_MAXAGE; -Promise.all([ - getIndexViaCdn(version), - getCurrentDashboardAnnouncementsIndexViaCdn(version), - getNextSyncUrl(version), -]).then(([index, dashIndex, next]) => { - publicIndex = index; - currentDashboardAnnouncementsMap = dashIndex; - nextSyncUrl = next; - updateCurrentDashboardAnnouncementId(); -}).catch(() => getIndex()); -*/ diff --git a/src/shared/components/Contentful/Article/Article.jsx b/src/shared/components/Contentful/Article/Article.jsx index 2beb028f37..c0ef0eae71 100644 --- a/src/shared/components/Contentful/Article/Article.jsx +++ b/src/shared/components/Contentful/Article/Article.jsx @@ -8,6 +8,8 @@ import PT from 'prop-types'; import { fixStyle } from 'utils/contentful'; import { getService } from 'services/contentful'; import MarkdownRenderer from 'components/MarkdownRenderer'; +import ReactDOMServer from 'react-dom/server'; +import markdown from 'utils/markdown'; import ContentfulLoader from 'containers/ContentfulLoader'; import LoadingIndicator from 'components/LoadingIndicator'; import YouTubeVideo from 'components/YouTubeVideo'; @@ -21,6 +23,8 @@ import UserDefault from 'assets/images/ico-user-default.svg'; import ReadMoreArrow from 'assets/images/read-more-arrow.svg'; import qs from 'qs'; +const htmlToText = require('html-to-text'); + // character length for the content preview const CONTENT_PREVIEW_LENGTH = 110; // Votes local storage key @@ -254,7 +258,17 @@ export default class Article extends React.Component {
    { - `${subData.entries.items[rec.sys.id].fields.content.substring(0, CONTENT_PREVIEW_LENGTH)}..` + `${htmlToText.fromString( + ReactDOMServer.renderToString(markdown( + subData.entries.items[rec.sys.id].fields.content, + )), + { + ignoreHref: true, + ignoreImage: true, + singleNewLineParagraphs: true, + uppercaseHeadings: false, + }, + ).substring(0, CONTENT_PREVIEW_LENGTH)}...` }
    { diff --git a/src/shared/components/Contentful/ArticleCard/ArticleCard.jsx b/src/shared/components/Contentful/ArticleCard/ArticleCard.jsx index e153e91d5d..59a6e794bd 100644 --- a/src/shared/components/Contentful/ArticleCard/ArticleCard.jsx +++ b/src/shared/components/Contentful/ArticleCard/ArticleCard.jsx @@ -12,6 +12,7 @@ import React from 'react'; import { themr } from 'react-css-super-themr'; import { config } from 'topcoder-react-utils'; import markdown from 'utils/markdown'; +import ReactDOMServer from 'react-dom/server'; // SVG assets import ThumbUpIcon from 'assets/images/ico-thumb-up.svg'; import CommentIcon from 'assets/images/ico-comment.svg'; @@ -21,17 +22,18 @@ import PlayIcon from 'assets/images/ico-play.svg'; import ReadMoreArrow from 'assets/images/read-more-arrow.svg'; import qs from 'qs'; +const htmlToText = require('html-to-text'); + // date/time format to use for the creation date const FORMAT = 'MMM DD, YYYY'; // date/time format for 'Forum post' cards is different from others const FORUM_POST_FORMAT = 'MMM DD, YYYY [at] h:mm A'; // max length for the title of the 'Article small' cards const ART_SMALL_TITLE_MAX_LENGTH = 29; -// character length for the content preview -const CONTENT_PREVIEW_LENGTH = 110; // Article large card 'breakpoint' const ARTICLE_LARGE_BREAKPOINT = 473; - +// character length for the content preview +const CONTENT_PREVIEW_LENGTH = 110; class ArticleCard extends React.Component { constructor(props) { @@ -132,10 +134,16 @@ class ArticleCard extends React.Component { : article.title; // truncate content for 'Article large' cards - const content = ( - (themeName === 'Article large' || themeName === 'Recommended') - && article.content.length > CONTENT_PREVIEW_LENGTH) - ? markdown(`${article.content.substring(0, CONTENT_PREVIEW_LENGTH)}...`) + const content = themeName === 'Article large' || themeName === 'Recommended' + ? `${htmlToText.fromString( + ReactDOMServer.renderToString(markdown(article.content)), + { + ignoreHref: true, + ignoreImage: true, + singleNewLineParagraphs: true, + uppercaseHeadings: false, + }, + ).substring(0, CONTENT_PREVIEW_LENGTH)}...` : undefined; // set the correct format to apply to the `article.creationDate` diff --git a/src/shared/components/SubmissionPage/Uploading/styles.scss b/src/shared/components/SubmissionPage/Uploading/styles.scss index 7dbf48d28d..d39f367a05 100644 --- a/src/shared/components/SubmissionPage/Uploading/styles.scss +++ b/src/shared/components/SubmissionPage/Uploading/styles.scss @@ -4,15 +4,14 @@ @include roboto-regular; align-items: center; - position: absolute; width: 100%; - height: calc(100vh); background-color: white; - top: -54px; left: 0; z-index: 100000; display: flex; justify-content: center; + position: relative; + padding: 30px; } .link, diff --git a/src/shared/components/SubmissionPage/styles.scss b/src/shared/components/SubmissionPage/styles.scss index 4fabe9d902..7837315c20 100644 --- a/src/shared/components/SubmissionPage/styles.scss +++ b/src/shared/components/SubmissionPage/styles.scss @@ -4,8 +4,8 @@ display: flex; flex: 1; justify-content: center; - padding: 30px 0 40px; - background: #f6f6f6; + padding: 80px 0 40px; + background: #fff; @include md-to-lg { padding-bottom: 0; diff --git a/src/shared/components/TopcoderFooter/index.jsx b/src/shared/components/TopcoderFooter/index.jsx index 70acbf4905..6f97a5c674 100644 --- a/src/shared/components/TopcoderFooter/index.jsx +++ b/src/shared/components/TopcoderFooter/index.jsx @@ -89,6 +89,7 @@ export default function TopcoderFooter() { Contact Us Join Community About Community + Changelog Talk to Sales
  • diff --git a/src/shared/containers/EDU/styles/tabs.scss b/src/shared/containers/EDU/styles/tabs.scss index 2345eae506..eee81a60b5 100644 --- a/src/shared/containers/EDU/styles/tabs.scss +++ b/src/shared/containers/EDU/styles/tabs.scss @@ -41,6 +41,7 @@ border-radius: 9px; margin-left: 9px; padding: 0 7px; + padding-top: 2px; } &.selected {