diff --git a/api/mutations/thread/editThread.js b/api/mutations/thread/editThread.js index 089a34e18d..fd6630db97 100644 --- a/api/mutations/thread/editThread.js +++ b/api/mutations/thread/editThread.js @@ -1,4 +1,5 @@ // @flow +const debug = require('debug')('api:mutations:edit-thread'); import type { GraphQLContext } from '../../'; import type { EditThreadInput } from '../../models/thread'; import UserError from '../../utils/UserError'; @@ -7,6 +8,7 @@ import { getThreads, editThread } from '../../models/thread'; import { getUserPermissionsInCommunity } from '../../models/usersCommunities'; import { getUserPermissionsInChannel } from '../../models/usersChannels'; import { isAuthedResolver as requireAuth } from '../../utils/permissions'; +import processThreadContent from 'shared/draft-utils/process-thread-content'; import { events } from 'shared/analytics'; import { trackQueue } from 'shared/bull/queues'; import { @@ -83,6 +85,8 @@ export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { ); } + input.content.body = processThreadContent('TEXT', input.content.body || ''); + /* When threads are sent to the client, all image urls are signed and proxied via imgix. If a user edits the thread, we have to restore all image upload @@ -130,6 +134,7 @@ export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => { }); } + debug('store new body to database:', initialBody); const newInput = Object.assign({}, input, { ...input, content: { diff --git a/api/utils/create-graphql-error-formatter.js b/api/utils/create-graphql-error-formatter.js index fd44d47315..6d1bcef947 100644 --- a/api/utils/create-graphql-error-formatter.js +++ b/api/utils/create-graphql-error-formatter.js @@ -32,6 +32,10 @@ const errorPath = error => { const logGraphQLError = (req, error) => { debug('---GraphQL Error---'); debug(error); + error && + error.extensions && + error.extensions.exception && + debug(error.extensions.exception.stacktrace.join('\n')); if (req) { debug(collectQueries(req.body.query)); debug('variables', JSON.stringify(req.body.variables || {})); diff --git a/cypress/integration/thread/action_bar_spec.js b/cypress/integration/thread/action_bar_spec.js index 643b0bcb26..c8b1d9d175 100644 --- a/cypress/integration/thread/action_bar_spec.js +++ b/cypress/integration/thread/action_bar_spec.js @@ -189,8 +189,8 @@ describe('action bar renders', () => { cy.get('[data-cy="thread-dropdown-edit"]').click(); cy.get('[data-cy="save-thread-edit-button"]').should('be.visible'); const title = 'Some new thread'; - cy.get('[data-cy="rich-text-editor"].markdown').should('be.visible'); - cy.get('[data-cy="thread-editor-title-input"]') + cy.get('[data-cy="rich-text-editor"]').should('be.visible'); + cy.get('[data-cy="composer-title-input"]') .clear() .type(title); cy.get('[data-cy="save-thread-edit-button"]').click(); @@ -201,8 +201,8 @@ describe('action bar renders', () => { cy.get('[data-cy="thread-dropdown-edit"]').click(); cy.get('[data-cy="save-thread-edit-button"]').should('be.visible'); const originalTitle = 'The first thread! 🎉'; - cy.get('[data-cy="rich-text-editor"].markdown').should('be.visible'); - cy.get('[data-cy="thread-editor-title-input"]') + cy.get('[data-cy="rich-text-editor"]').should('be.visible'); + cy.get('[data-cy="composer-title-input"]') .clear() .type(originalTitle); cy.get('[data-cy="save-thread-edit-button"]').click(); diff --git a/flow-typed/npm/prism-react-renderer_vx.x.x.js b/flow-typed/npm/prism-react-renderer_vx.x.x.js new file mode 100644 index 0000000000..b37a4f1ad0 --- /dev/null +++ b/flow-typed/npm/prism-react-renderer_vx.x.x.js @@ -0,0 +1,214 @@ +// flow-typed signature: 3987fe684d034fe6822d30ca25b56da6 +// flow-typed version: <>/prism-react-renderer_vx.x.x/flow_v0.66.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'prism-react-renderer' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'prism-react-renderer' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'prism-react-renderer/es/components/Highlight' { + declare module.exports: any; +} + +declare module 'prism-react-renderer/es/defaultProps' { + declare module.exports: any; +} + +declare module 'prism-react-renderer/es/index' { + declare module.exports: any; +} + +declare module 'prism-react-renderer/es/types' { + declare module.exports: any; +} + +declare module 'prism-react-renderer/es/utils/normalizeTokens' { + declare module.exports: any; +} + +declare module 'prism-react-renderer/es/utils/themeToDict' { + declare module.exports: any; +} + +declare module 'prism-react-renderer/es/vendor/prism/includeLangs' { + declare module.exports: any; +} + +declare module 'prism-react-renderer/es/vendor/prism/index' { + declare module.exports: any; +} + +declare module 'prism-react-renderer/es/vendor/prism/prism-core' { + declare module.exports: any; +} + +declare module 'prism-react-renderer/lib/components/Highlight' { + declare module.exports: any; +} + +declare module 'prism-react-renderer/lib/defaultProps' { + declare module.exports: any; +} + +declare module 'prism-react-renderer/lib/index' { + declare module.exports: any; +} + +declare module 'prism-react-renderer/lib/types' { + declare module.exports: any; +} + +declare module 'prism-react-renderer/lib/utils/normalizeTokens' { + declare module.exports: any; +} + +declare module 'prism-react-renderer/lib/utils/themeToDict' { + declare module.exports: any; +} + +declare module 'prism-react-renderer/lib/vendor/prism/includeLangs' { + declare module.exports: any; +} + +declare module 'prism-react-renderer/lib/vendor/prism/index' { + declare module.exports: any; +} + +declare module 'prism-react-renderer/lib/vendor/prism/prism-core' { + declare module.exports: any; +} + +declare module 'prism-react-renderer/themes/dracula' { + declare module.exports: any; +} + +declare module 'prism-react-renderer/themes/duotoneDark' { + declare module.exports: any; +} + +declare module 'prism-react-renderer/themes/duotoneLight' { + declare module.exports: any; +} + +declare module 'prism-react-renderer/themes/nightOwl' { + declare module.exports: any; +} + +declare module 'prism-react-renderer/themes/oceanicNext' { + declare module.exports: any; +} + +declare module 'prism-react-renderer/themes/shadesOfPurple' { + declare module.exports: any; +} + +declare module 'prism-react-renderer/themes/ultramin' { + declare module.exports: any; +} + +declare module 'prism-react-renderer/themes/vsDark' { + declare module.exports: any; +} + +declare module 'prism-react-renderer/themes/vsDarkPlus' { + declare module.exports: any; +} + +// Filename aliases +declare module 'prism-react-renderer/es/components/Highlight.js' { + declare module.exports: $Exports<'prism-react-renderer/es/components/Highlight'>; +} +declare module 'prism-react-renderer/es/defaultProps.js' { + declare module.exports: $Exports<'prism-react-renderer/es/defaultProps'>; +} +declare module 'prism-react-renderer/es/index.js' { + declare module.exports: $Exports<'prism-react-renderer/es/index'>; +} +declare module 'prism-react-renderer/es/types.js' { + declare module.exports: $Exports<'prism-react-renderer/es/types'>; +} +declare module 'prism-react-renderer/es/utils/normalizeTokens.js' { + declare module.exports: $Exports<'prism-react-renderer/es/utils/normalizeTokens'>; +} +declare module 'prism-react-renderer/es/utils/themeToDict.js' { + declare module.exports: $Exports<'prism-react-renderer/es/utils/themeToDict'>; +} +declare module 'prism-react-renderer/es/vendor/prism/includeLangs.js' { + declare module.exports: $Exports<'prism-react-renderer/es/vendor/prism/includeLangs'>; +} +declare module 'prism-react-renderer/es/vendor/prism/index.js' { + declare module.exports: $Exports<'prism-react-renderer/es/vendor/prism/index'>; +} +declare module 'prism-react-renderer/es/vendor/prism/prism-core.js' { + declare module.exports: $Exports<'prism-react-renderer/es/vendor/prism/prism-core'>; +} +declare module 'prism-react-renderer/lib/components/Highlight.js' { + declare module.exports: $Exports<'prism-react-renderer/lib/components/Highlight'>; +} +declare module 'prism-react-renderer/lib/defaultProps.js' { + declare module.exports: $Exports<'prism-react-renderer/lib/defaultProps'>; +} +declare module 'prism-react-renderer/lib/index.js' { + declare module.exports: $Exports<'prism-react-renderer/lib/index'>; +} +declare module 'prism-react-renderer/lib/types.js' { + declare module.exports: $Exports<'prism-react-renderer/lib/types'>; +} +declare module 'prism-react-renderer/lib/utils/normalizeTokens.js' { + declare module.exports: $Exports<'prism-react-renderer/lib/utils/normalizeTokens'>; +} +declare module 'prism-react-renderer/lib/utils/themeToDict.js' { + declare module.exports: $Exports<'prism-react-renderer/lib/utils/themeToDict'>; +} +declare module 'prism-react-renderer/lib/vendor/prism/includeLangs.js' { + declare module.exports: $Exports<'prism-react-renderer/lib/vendor/prism/includeLangs'>; +} +declare module 'prism-react-renderer/lib/vendor/prism/index.js' { + declare module.exports: $Exports<'prism-react-renderer/lib/vendor/prism/index'>; +} +declare module 'prism-react-renderer/lib/vendor/prism/prism-core.js' { + declare module.exports: $Exports<'prism-react-renderer/lib/vendor/prism/prism-core'>; +} +declare module 'prism-react-renderer/themes/dracula.js' { + declare module.exports: $Exports<'prism-react-renderer/themes/dracula'>; +} +declare module 'prism-react-renderer/themes/duotoneDark.js' { + declare module.exports: $Exports<'prism-react-renderer/themes/duotoneDark'>; +} +declare module 'prism-react-renderer/themes/duotoneLight.js' { + declare module.exports: $Exports<'prism-react-renderer/themes/duotoneLight'>; +} +declare module 'prism-react-renderer/themes/nightOwl.js' { + declare module.exports: $Exports<'prism-react-renderer/themes/nightOwl'>; +} +declare module 'prism-react-renderer/themes/oceanicNext.js' { + declare module.exports: $Exports<'prism-react-renderer/themes/oceanicNext'>; +} +declare module 'prism-react-renderer/themes/shadesOfPurple.js' { + declare module.exports: $Exports<'prism-react-renderer/themes/shadesOfPurple'>; +} +declare module 'prism-react-renderer/themes/ultramin.js' { + declare module.exports: $Exports<'prism-react-renderer/themes/ultramin'>; +} +declare module 'prism-react-renderer/themes/vsDark.js' { + declare module.exports: $Exports<'prism-react-renderer/themes/vsDark'>; +} +declare module 'prism-react-renderer/themes/vsDarkPlus.js' { + declare module.exports: $Exports<'prism-react-renderer/themes/vsDarkPlus'>; +} diff --git a/package.json b/package.json index 4324d02a86..9f5ac9c709 100644 --- a/package.json +++ b/package.json @@ -146,6 +146,7 @@ "passport-google-oauth2": "^0.1.6", "passport-twitter": "^1.0.4", "pre-commit": "^1.2.2", + "prism-react-renderer": "^0.1.5", "prismjs": "^1.15.0", "query-string": "5.1.1", "raf": "^3.4.0", diff --git a/shared/clients/draft-js/message/types.js b/shared/clients/draft-js/message/types.js index f4da3006f4..9ba5957568 100644 --- a/shared/clients/draft-js/message/types.js +++ b/shared/clients/draft-js/message/types.js @@ -6,8 +6,10 @@ export type KeyObj = { export type KeysObj = { keys: string[], + data?: Object, }; export type DataObj = { - url: string, + url?: string, + href?: string, }; diff --git a/shared/clients/draft-js/thread/renderer.js b/shared/clients/draft-js/thread/renderer.js new file mode 100644 index 0000000000..75362ead5d --- /dev/null +++ b/shared/clients/draft-js/thread/renderer.js @@ -0,0 +1,172 @@ +// @flow +import React from 'react'; +import Highlight, { defaultProps } from 'prism-react-renderer'; +import { Line, Paragraph, BlockQuote } from 'src/components/message/style'; +import { + AspectRatio, + EmbedContainer, + EmbedComponent, +} from 'src/components/rich-text-editor/style'; +import mentionsDecorator from '../mentions-decorator'; +import linksDecorator from '../links-decorator'; +import type { Node } from 'react'; +import type { KeyObj, KeysObj, DataObj } from '../message/types'; + +type EmbedProps = { + aspectRatio?: string, + src: string, + width?: string | number, + height?: string | number, +}; + +const Embed = (props: EmbedProps) => { + const { aspectRatio, src, width = '100%', height = 200 } = props; + + if (!src) return null; + + // if an aspect ratio is passed in, we need to use the EmbedComponent which does some trickery with padding to force an aspect ratio. Otherwise we should just use a regular iFrame + if (aspectRatio && aspectRatio !== undefined) { + return ( + + + + ); + } else { + return ( + +