diff --git a/client/apps/edit/components/content/sections/image_collection/index.coffee b/client/apps/edit/components/content/sections/image_collection/index.coffee index e37515b63..767be3ebf 100644 --- a/client/apps/edit/components/content/sections/image_collection/index.coffee +++ b/client/apps/edit/components/content/sections/image_collection/index.coffee @@ -12,7 +12,7 @@ DragContainer = React.createFactory require '../../../../../../components/drag_d { div, section, ul, li } = React.DOM components = require('@artsy/reaction-force/dist/components/publishing/index').default -ImageSetPreview = React.createFactory components.ImagesetPreviewClassic +ImageSetPreview = React.createFactory components.ImageSetPreviewClassic module.exports = React.createClass displayName: 'SectionImageCollection' diff --git a/client/apps/edit/components/content2/section_container/index.coffee b/client/apps/edit/components/content2/section_container/index.coffee index e3c34676d..d32b380d6 100644 --- a/client/apps/edit/components/content2/section_container/index.coffee +++ b/client/apps/edit/components/content2/section_container/index.coffee @@ -6,14 +6,13 @@ React = require 'react' _ = require 'underscore' -SectionText = React.createFactory require '../sections/text/index.coffee' -SectionVideo = React.createFactory require '../../content/sections/video/index.coffee' -SectionSlideshow = React.createFactory require '../../content/sections/slideshow/index.coffee' -SectionEmbed = React.createFactory require '../../content/sections/embed/index.coffee' -SectionFullscreen = React.createFactory require '../../content/sections/fullscreen/index.coffee' -SectionCallout = React.createFactory require '../../content/sections/callout/index.coffee' -SectionImageCollection = React.createFactory require '../sections/image_collection/index.coffee' -SectionImage = React.createFactory require '../../content/sections/image/index.coffee' +Text = React.createFactory require '../sections/text/index.coffee' +Video = React.createFactory require '../../content/sections/video/index.coffee' +Slideshow = React.createFactory require '../../content/sections/slideshow/index.coffee' +Embed = React.createFactory require '../../content/sections/embed/index.coffee' +Fullscreen = React.createFactory require '../../content/sections/fullscreen/index.coffee' +ImageCollection = React.createFactory require '../sections/image_collection/index.coffee' +Image = React.createFactory require '../../content/sections/image/index.coffee' { div, nav, button } = React.DOM icons = -> require('../../icons.jade') arguments... @@ -64,15 +63,14 @@ module.exports = React.createClass dangerouslySetInnerHTML: __html: $(icons()).filter('.remove').html() } (switch @props.section.get('type') - when 'text' then SectionText - when 'video' then SectionVideo - when 'slideshow' then SectionSlideshow - when 'embed' then SectionEmbed - when 'fullscreen' then SectionFullscreen - when 'callout' then SectionCallout - when 'image_set' then SectionImageCollection - when 'image_collection' then SectionImageCollection - when 'image' then SectionImage + when 'text' then Text + when 'video' then Video + when 'slideshow' then Slideshow + when 'embed' then Embed + when 'fullscreen' then Fullscreen + when 'image_set' then ImageCollection + when 'image_collection' then ImageCollection + when 'image' then Image )( section: @props.section sections: @props.sections diff --git a/client/apps/edit/components/content2/section_container/test/index.coffee b/client/apps/edit/components/content2/section_container/test/index.coffee index cd3673433..73469ba09 100644 --- a/client/apps/edit/components/content2/section_container/test/index.coffee +++ b/client/apps/edit/components/content2/section_container/test/index.coffee @@ -38,7 +38,7 @@ describe 'SectionContainer', -> dragOver: 4 article: new Article } - @SectionContainer.__set__ 'SectionText', -> + @SectionContainer.__set__ 'Text', -> @component = ReactDOM.render React.createElement(@SectionContainer, @props ), $("
")[0], => setTimeout => @component.setState = sinon.stub() diff --git a/client/apps/edit/components/content2/section_list/index.coffee b/client/apps/edit/components/content2/section_list/index.coffee index e7cd85847..e90bb9a27 100644 --- a/client/apps/edit/components/content2/section_list/index.coffee +++ b/client/apps/edit/components/content2/section_list/index.coffee @@ -7,7 +7,7 @@ React = require 'react' SectionContainer = React.createFactory require '../section_container/index.coffee' SectionTool = React.createFactory require '../section_tool/index.coffee' DragContainer = React.createFactory require '../../../../../components/drag_drop/index.coffee' -RichTextParagraph = React.createFactory require '../../../../../components/rich_text/components/input_paragraph.coffee' +Paragraph = React.createFactory require '../../../../../components/rich_text2/components/paragraph.coffee' { div } = React.DOM module.exports = React.createClass @@ -39,7 +39,7 @@ module.exports = React.createClass if @state.editingIndex or @state.editingIndex is 0 then false else true setPostscript: (html) -> - html = null if html is '' + html = null unless html.length @props.article.set('postscript', html) @props.saveArticle() @@ -50,7 +50,7 @@ module.exports = React.createClass ref: 'sections' }, SectionTool { sections: @props.sections, index: -1, key: 1 } - if @props.sections.length > 0 + if @props.sections.length DragContainer { items: @props.sections.models onDragEnd: @onDragEnd @@ -59,27 +59,32 @@ module.exports = React.createClass article: @props.article }, @props.sections.map (section, i) => - [ - SectionContainer { - sections: @props.sections - section: section - index: i - editing: @state.editingIndex is i - ref: 'section' + i - key: section.cid - channel: @props.channel - onSetEditing: @onSetEditing - article: @props.article - } - SectionTool { sections: @props.sections, index: i, key: i } - ] + unless section.get('type') is 'callout' + [ + SectionContainer { + sections: @props.sections + section: section + index: i + editing: @state.editingIndex is i + ref: 'section' + i + key: section.cid + channel: @props.channel + onSetEditing: @onSetEditing + article: @props.article + } + SectionTool { sections: @props.sections, index: i, key: i } + ] if @props.channel.isEditorial() div { className: 'edit-sections__postscript' 'data-layout': 'column_width' }, - RichTextParagraph { - text: @props.article.get('postscript') or '' + Paragraph { + html: @props.article.get('postscript') or '' onChange: @setPostscript placeholder: 'Postscript (optional)' + type: 'postscript' + linked: true + layout: @props.article.get('layout') } + # TODO - Author Preview diff --git a/client/apps/edit/components/content2/section_list/index.styl b/client/apps/edit/components/content2/section_list/index.styl index 2c2994c4a..72534bf2e 100644 --- a/client/apps/edit/components/content2/section_list/index.styl +++ b/client/apps/edit/components/content2/section_list/index.styl @@ -6,15 +6,13 @@ .edit-sections__postscript max-width standard-body-w-margin margin 0 auto - padding 60px 20px 20px - font-style italic - Garamond s23 - p - margin-top 1em - margin-bottom 1em + padding 20px .public-DraftEditorPlaceholder-root position absolute color gray-dark-color + top 27px + Garamond s23 + font-style italic .standard max-width standard-max-width diff --git a/client/apps/edit/components/content2/section_list/test/index.coffee b/client/apps/edit/components/content2/section_list/test/index.coffee index 3174a9010..43ecb7e57 100644 --- a/client/apps/edit/components/content2/section_list/test/index.coffee +++ b/client/apps/edit/components/content2/section_list/test/index.coffee @@ -20,18 +20,18 @@ describe 'SectionList', -> global.HTMLElement = () => {} @SectionList = benv.require resolve(__dirname, '../index') DragContainer = benv.require resolve(__dirname, '../../../../../../components/drag_drop/index') - RichTextParagraph = benv.require resolve( - __dirname, '../../../../../../components/rich_text/components/input_paragraph.coffee' + Paragraph = benv.require resolve( + __dirname, '../../../../../../components/rich_text2/components/paragraph.coffee' ) @SectionList.__set__ 'SectionTool', @SectionTool = sinon.stub() @SectionContainer = benv.requireWithJadeify( resolve(__dirname, '../../section_container/index'), ['icons'] ) - @SectionContainer.__set__ 'SectionText', text = sinon.stub() - @SectionContainer.__set__ 'SectionImageCollection', image_collection = sinon.stub() + @SectionContainer.__set__ 'Text', text = sinon.stub() + @SectionContainer.__set__ 'ImageCollection', image_collection = sinon.stub() @SectionList.__set__ 'SectionContainer', React.createFactory @SectionContainer @SectionList.__set__ 'DragContainer', React.createFactory DragContainer - @SectionList.__set__ 'RichTextParagraph', React.createFactory RichTextParagraph + @SectionList.__set__ 'Paragraph', React.createFactory Paragraph @props = { article: new Backbone.Model {layout: 'feature'} sections: @sections = new Sections [ diff --git a/client/apps/edit/components/content2/sections/header/index.coffee b/client/apps/edit/components/content2/sections/header/index.coffee index ef76f9e0b..39b27d58e 100644 --- a/client/apps/edit/components/content2/sections/header/index.coffee +++ b/client/apps/edit/components/content2/sections/header/index.coffee @@ -1,9 +1,8 @@ React = require 'react' moment = require 'moment' -RichTextParagraph = React.createFactory require '../../../../../../components/rich_text2/components/input_paragraph.coffee' +Paragraph = React.createFactory require '../../../../../../components/rich_text2/components/paragraph.coffee' { div, p, textarea } = React.DOM - module.exports = React.createClass displayName: 'SectionHeader' @@ -23,10 +22,14 @@ module.exports = React.createClass div { className: 'edit-header__lead-paragraph' }, - RichTextParagraph { - text: @props.article.get('lead_paragraph') + Paragraph { + html: @props.article.get('lead_paragraph') onChange: @setLeadParagraph - placeholder: 'Lead paragraph (optional)' + placeholder: 'Lead Paragraph (optional)' + type: 'lead_paragraph' + linked: false + linebreaks: false + layout: @props.article.get('layout') } render: -> @@ -48,7 +51,7 @@ module.exports = React.createClass onKeyUp: @setTitle ref: 'title' } - unless @props.article.get('title')?.length > 0 + unless @props.article.get('title')?.length div { className: 'edit-required' } if layout is 'classic' @@ -60,5 +63,5 @@ module.exports = React.createClass @props.article.get('author').name p { className: 'date' }, @props.article.getPublishDate() - if layout is 'standard' and @props.article.get('lead_paragraph').length + if layout is 'standard' and @props.article.get('lead_paragraph')?.length @renderLeadParagraph() diff --git a/client/apps/edit/components/content2/sections/header/index.styl b/client/apps/edit/components/content2/sections/header/index.styl index 652a30121..f51c4cc95 100644 --- a/client/apps/edit/components/content2/sections/header/index.styl +++ b/client/apps/edit/components/content2/sections/header/index.styl @@ -2,6 +2,7 @@ padding-top 60px .public-DraftEditorPlaceholder-root position absolute + top 1em color gray-dark-color &__title position relative @@ -82,5 +83,4 @@ garamond l-body max-width classic-body-width margin 20px auto 0 auto - .public-DraftEditorPlaceholder-root - color gray-dark-color + diff --git a/client/apps/edit/components/content2/sections/header/test/index.coffee b/client/apps/edit/components/content2/sections/header/test/index.coffee index cf56cf482..21bcbf062 100644 --- a/client/apps/edit/components/content2/sections/header/test/index.coffee +++ b/client/apps/edit/components/content2/sections/header/test/index.coffee @@ -19,8 +19,8 @@ describe 'SectionHeader: Classic', -> benv.expose $: benv.require 'jquery' global.HTMLElement = () => {} SectionHeader = benv.require resolve(__dirname, '../index.coffee') - RichTextParagraph = benv.require resolve(__dirname, '../../../../../../../components/rich_text2/components/input_paragraph.coffee') - SectionHeader.__set__ 'RichTextParagraph', React.createFactory RichTextParagraph + Paragraph = benv.require resolve(__dirname, '../../../../../../../components/rich_text2/components/paragraph.coffee') + SectionHeader.__set__ 'Paragraph', React.createFactory Paragraph @article = new Article _.extend fixtures().articles, layout: 'classic' author: @@ -69,7 +69,7 @@ describe 'SectionHeader: Classic', -> describe 'Lead Paragraph', -> it 'renders a lead paragraph component', -> - $(ReactDOM.findDOMNode(@component)).html().should.containEql 'A new paragraph
') diff --git a/client/apps/edit/components/content2/sections/image_collection/index.coffee b/client/apps/edit/components/content2/sections/image_collection/index.coffee index 67f6ba7e1..3103904c7 100644 --- a/client/apps/edit/components/content2/sections/image_collection/index.coffee +++ b/client/apps/edit/components/content2/sections/image_collection/index.coffee @@ -10,9 +10,8 @@ Controls = React.createFactory require './components/controls.coffee' DragContainer = React.createFactory require '../../../../../../components/drag_drop/index.coffee' { fillWidth } = require '../../../../../../components/fill_width/index.coffee' { div, section, ul, li } = React.DOM - components = require('@artsy/reaction-force/dist/components/publishing/index').default -ImagesetPreviewClassic = React.createFactory components.ImagesetPreviewClassic +ImageSetPreviewClassic = React.createFactory components.ImageSetPreviewClassic module.exports = React.createClass displayName: 'SectionImageCollection' @@ -115,7 +114,7 @@ module.exports = React.createClass }, if hasImages if !@props.editing and @props.section.get('type') is 'image_set' - ImagesetPreviewClassic { + ImageSetPreviewClassic { images: images } else diff --git a/client/apps/edit/components/content2/sections/text/index.coffee b/client/apps/edit/components/content2/sections/text/index.coffee index 0feb03b19..775711d9b 100644 --- a/client/apps/edit/components/content2/sections/text/index.coffee +++ b/client/apps/edit/components/content2/sections/text/index.coffee @@ -3,39 +3,27 @@ ReactDOM = require 'react-dom' _s = require 'underscore.string' sd = require('sharify').data window.process = {env: {NODE_ENV: sd.NODE_ENV}} +components = require('@artsy/reaction-force/dist/components/publishing/index').default +Config = require './draft_config.coffee' +Nav = React.createFactory require '../../../../../../components/rich_text2/components/nav.coffee' +InputUrl = React.createFactory require '../../../../../../components/rich_text2/components/input_url.coffee' +Text = React.createFactory components.Text +Utils = require '../../../../../../components/rich_text2/utils/index.coffee' { CompositeDecorator, ContentState, Editor, EditorState, RichUtils, Modifier } = require 'draft-js' -{ blockTypes, - blockRenderMap, - decorators, - inlineStyles } = require './draft_config.coffee' -{ convertFromRichHtml, - convertToRichHtml, - getExistingLinkData, - getSelectionDetails, - getSelectionLocation, - keyBindingFnFull, - moveSelection, - setContentStartEnd, - setSelectionToStart, - standardizeSpacing, - stickyControlsBox, - stripCharacterStyles, - stripGoogleStyles } = require '../../../../../../components/rich_text2/utils/index.coffee' editor = (props) -> React.createElement Editor, props { div } = React.DOM -EditNav = React.createFactory require '../../../../../../components/rich_text2/components/edit_nav.coffee' -InputUrl = React.createFactory require '../../../../../../components/rich_text2/components/input_url.coffee' + module.exports = React.createClass displayName: 'SectionText' getInitialState: -> - editorState: EditorState.createEmpty(new CompositeDecorator(decorators(@props.article.get('layout')))) + editorState: EditorState.createEmpty(new CompositeDecorator(Config.decorators(@props.article.get('layout')))) focus: false html: null selectionTarget: null @@ -47,12 +35,12 @@ module.exports = React.createClass componentDidMount: -> if @props.section.get('body')?.length - html = standardizeSpacing @props.section.get('body') + html = Utils.standardizeSpacing @props.section.get('body') unless @props.article.get('layout') is 'classic' - html = setContentStartEnd(html, @props.article.get('layout'), @props.isStartText, @props.isEndText) - blocksFromHTML = convertFromRichHtml html - editorState = EditorState.createWithContent(blocksFromHTML, new CompositeDecorator(decorators(@props.article.get('layout')))) - editorState = setSelectionToStart(editorState) if @props.editing + html = Utils.setContentStartEnd(html, @props.article.get('layout'), @props.isStartText, @props.isEndText) + blocksFromHTML = Utils.convertFromRichHtml html + editorState = EditorState.createWithContent(blocksFromHTML, new CompositeDecorator(Config.decorators(@props.article.get('layout')))) + editorState = Utils.setSelectionToStart(editorState) if @props.editing @setState html: html editorState: editorState @@ -62,7 +50,7 @@ module.exports = React.createClass componentDidUpdate: (prevProps) -> if @props.isEndText isnt prevProps.isEndText or @props.isStartText isnt prevProps.isStartText unless @props.article.get('layout') is 'classic' - html = setContentStartEnd(@props.section.get('body'), @props.article.get('layout'), @props.isStartText, @props.isEndText) + html = Utils.setContentStartEnd(@props.section.get('body'), @props.article.get('layout'), @props.isStartText, @props.isEndText) @props.section.set('body', html) if @props.editing and @props.editing isnt prevProps.editing @focus() @@ -70,7 +58,7 @@ module.exports = React.createClass @refs.editor.blur() onChange: (editorState) -> - html = convertToRichHtml editorState, @props.article.get('layout') + html = Utils.convertToRichHtml editorState, @props.article.get('layout') @setState editorState: editorState, html: html @props.section.set('body', html) @@ -84,7 +72,7 @@ module.exports = React.createClass @refs.editor.focus() handleReturn: (e) -> - selection = getSelectionDetails(@state.editorState) + selection = Utils.getSelectionDetails(@state.editorState) # dont split from the first block, to avoid creating empty blocks # dont split from the middle of a paragraph if selection.isFirstBlock or selection.anchorOffset @@ -102,16 +90,16 @@ module.exports = React.createClass @props.onSetEditing index handleBackspace: (e) -> - selection = getSelectionDetails(@state.editorState) + selection = Utils.getSelectionDetails(@state.editorState) # only merge a section if cursor is in first character of first block if selection.isFirstBlock and selection.anchorOffset is 0 and - @props.sections.models[@props.index - 1].get('type') is 'text' + @props.sections.models[@props.index - 1]?.get('type') is 'text' mergeIntoHTML = @props.sections.models[@props.index - 1].get('body') @props.sections.models[@props.index - 1].destroy() newHTML = mergeIntoHTML + @state.html - blocksFromHTML = convertFromRichHtml newHTML + blocksFromHTML = Utils.convertFromRichHtml newHTML newSectionState = EditorState.push(@state.editorState, blocksFromHTML, null) - newSectionState = setSelectionToStart newSectionState + newSectionState = Utils.setSelectionToStart newSectionState @onChange newSectionState @props.onSetEditing @props.index - 1 @@ -119,7 +107,7 @@ module.exports = React.createClass direction = 0 direction = -1 if e.key in ['ArrowUp', 'ArrowLeft'] direction = 1 if e.key in ['ArrowDown', 'ArrowRight'] - selection = getSelectionDetails @state.editorState + selection = Utils.getSelectionDetails @state.editorState # if cursor is arrowing forward from last charachter of last block, # or cursor is arrowing back from first character of first block, # jump to adjacent section @@ -129,7 +117,7 @@ module.exports = React.createClass else if e.key in ['ArrowLeft', 'ArrowRight'] # manually move cursor to make up for draft's missing l/r arrow fallbacks shift = if e.shiftKey then true else false - newEditorState = moveSelection @state.editorState, selection, direction, shift + newEditorState = Utils.moveSelection @state.editorState, selection, direction, shift @onChange(newEditorState) else return true @@ -146,7 +134,7 @@ module.exports = React.createClass currentState = EditorState.push(editorState, currentContent, 'remove-range') newSectionContent = ContentState.createFromBlockArray newBlockArray newSectionState = EditorState.push(editorState, newSectionContent, null) - newSectionHtml = convertToRichHtml(newSectionState) + newSectionHtml = Utils.convertToRichHtml(newSectionState) @onChange currentState @props.sections.add {type: 'text', body: newSectionHtml}, {at: @props.index + 1} return 'handled' @@ -155,10 +143,10 @@ module.exports = React.createClass { editorState } = @state unless html html = 'In 2016, K mounted a solo show at prescient Berlin gallery KOW, restaging his installation It’s Spring and the Weather is Great so let’s close all object matters (2012), for which he created seven step ladders with microphones and instruments attached for a performance initially meant to take place at Speakers’ Corner in London’s Hyde Park that was eventually mounted in 2010 at the Serpentine Galleries.
/g, '') + return html + + onPaste: (text, html) -> + { editorState } = @state + unless html + html = '
/g, '') + blocksFromHTML = @convertFromHTML html + convertedHtml = blocksFromHTML.getBlocksAsArray().map (contentBlock) => + unstyled = Utils.stripCharacterStyles contentBlock, true + unless unstyled.getType() in @availableBlocks() or unstyled.getType() is 'LINK' + unstyled = unstyled.set 'type', 'unstyled' + return unstyled + blockMap = ContentState.createFromBlockArray(convertedHtml, blocksFromHTML.getBlocksAsArray()).blockMap + newState = Modifier.replaceWithFragment( + editorState.getCurrentContent() + editorState.getSelection() + blockMap + ) + newState = EditorState.push(editorState, newState, 'insert-fragment') + @onChange newState + return true + + promptForLink: (e) -> + { editorState } = @state + e.preventDefault() if e + selection = editorState.getSelection() + selectionTarget = {top: 0, left: 0} + url = '' + if !selection.isCollapsed() + location = Utils.getSelectionLocation $(ReactDOM.findDOMNode(@refs.editor)).offset() + selectionTarget = Utils.stickyControlsBox(location, 25, 200) + contentState = editorState.getCurrentContent() + startKey = selection.getStartKey() + startOffset = selection.getStartOffset() + blockWithLinkAtBeginning = contentState.getBlockForKey(startKey) + linkKey = blockWithLinkAtBeginning.getEntityAt(startOffset) + if linkKey + linkInstance = contentState.getEntity(linkKey) + url = linkInstance.getData().url + @setState + showUrlInput: true + showNav: false + urlValue: url + selectionTarget: selectionTarget + + confirmLink: (url) -> + { editorState } = @state + contentState = editorState.getCurrentContent() + contentStateWithEntity = contentState.createEntity( + 'LINK' + 'MUTABLE' + {url: url} + ) + entityKey = contentStateWithEntity.getLastCreatedEntityKey() + newEditorState = EditorState.set editorState, { currentContent: contentStateWithEntity } + @setState({ + showUrlInput: false + showNav: false + urlValue: '' + selectionTarget: null + }) + @onChange RichUtils.toggleLink( + newEditorState + newEditorState.getSelection() + entityKey + ) + + removeLink: (e) -> + e.preventDefault() + { editorState } = @state + selection = editorState.getSelection() + if !selection.isCollapsed() + @setState({ + showUrlInput: false + urlValue: '' + editorState: RichUtils.toggleLink(editorState, selection, null) + }) + + printUrlInput: -> + if @state.showUrlInput + InputUrl { + selectionTarget: @state.selectionTarget + removeLink: @removeLink + confirmLink: @confirmLink + urlValue: @state.urlValue + } + + checkSelection: -> + if !window.getSelection().isCollapsed + location = Utils.getSelectionLocation $(ReactDOM.findDOMNode(@refs.editor)).offset() + selectionTargetL = Config.inlineStyles(@props.type).length * 25 + selectionTargetL = selectionTargetL + 25 if @hasLinks() + @setState showNav: true, selectionTarget: Utils.stickyControlsBox(location, -43, selectionTargetL) + else + @setState showNav: false + + render: -> + Text { + layout: @props.layout + postscript: @props.type is 'postscript' + }, + if @state.showNav + Nav { + styles: Config.inlineStyles(@props.type) + toggleStyle: @toggleInlineStyle + promptForLink: @promptForLink if @hasLinks() + position: @state.selectionTarget + } + div { + className: 'rich-text--paragraph' + onClick: @focus + onBlur: @onBlur + onMouseUp: @checkSelection + onKeyUp: @checkSelection + }, + editor { + ref: 'editor' + placeholder: @props.placeholder + editorState: @state.editorState + spellCheck: true + onChange: @onChange + blockRenderMap: Config.blockRenderMap() + handleReturn: @handleReturn + handleKeyCommand: @handleKeyCommand + keyBindingFn: Utils.keyBindingFnCaption + handlePastedText: @onPaste + } + @printUrlInput() diff --git a/client/components/rich_text2/index.styl b/client/components/rich_text2/index.styl index 8214e4872..e4c90d7e8 100644 --- a/client/components/rich_text2/index.styl +++ b/client/components/rich_text2/index.styl @@ -81,6 +81,8 @@ border none border-right 1px solid gray-darkest-color transition color .15s + &:last-child + border-right none &:hover color gray-color &[name='ITALIC'], diff --git a/client/components/rich_text2/test/input_paragraph.test.coffee b/client/components/rich_text2/test/input_paragraph.test.coffee deleted file mode 100644 index 7cb8b753b..000000000 --- a/client/components/rich_text2/test/input_paragraph.test.coffee +++ /dev/null @@ -1,96 +0,0 @@ -benv = require 'benv' -{ resolve } = require 'path' -sinon = require 'sinon' - -# FIXME: Invariant Violation: dangerouslyReplaceNodeWithMarkup(...): Cannot render markup in a worker thread. Make sure `window` and `document` are available globally before requiring React when unit testing or use ReactDOMServer.renderToString() for server rendering. -describe.skip 'RichTextParagraph', -> - - beforeEach (done) -> - benv.setup => - benv.expose - $: benv.require 'jquery' - React: require 'react' - ReactDOM: require 'react-dom' - ReactTestUtils: require 'react-addons-test-utils' - ReactDOMServer: require 'react-dom/server' - Draft: require 'draft-js' - window.jQuery = $ - @r = - find: ReactTestUtils.findRenderedDOMComponentWithClass - simulate: ReactTestUtils.Simulate - @d = - EditorState: Draft.EditorState - global.HTMLElement = () => {} - global.HTMLAnchorElement = () => {} - @RichTextParagraph = benv.require resolve __dirname, '../components/input_paragraph' - @props = { - text: '
Here is the lead paragraph for this article.
' - placeholder: 'Lead paragraph (optional)' - onChange: sinon.stub() - } - @rendered = ReactDOMServer.renderToString React.createElement(@RichTextParagraph, @props) - - @component = ReactDOM.render React.createElement(@RichTextParagraph, @props), (@$el = $ "")[0], => setTimeout => - @component.state.editorState.getSelection().isCollapsed = sinon.stub().returns false - selection = @component.state.editorState.getSelection() - newSelection = selection.merge({ - anchorKey: @component.state.editorState.getCurrentContent().getFirstBlock().key - anchorOffset: 0 - focusKey: @component.state.editorState.getCurrentContent().getFirstBlock().key - focusOffset: 7 - }) - newEditorState = @d.EditorState.acceptSelection(@component.state.editorState, newSelection) - @component.onChange newEditorState - done() - - afterEach -> - benv.teardown() - - it 'Shows a placeholder if provided and empty', -> - $(@rendered).text().should.eql 'Lead paragraph (optional)' - - it 'Renders an existing lead paragraph', -> - $(ReactDOM.findDOMNode(@component)).text().should.eql 'Here is the lead paragraph for this article.' - @component.state.html.should.eql 'Here is the lead paragraph for this article.
' - - it 'Can toggle bold styles by default', -> - @component.handleKeyCommand('bold') - @component.state.html.should.eql 'Here is the lead paragraph for this article.
' - - it 'Can toggle italic styles by default', -> - @component.handleKeyCommand('italic') - @component.state.html.should.eql 'Here is the lead paragraph for this article.
' - - it 'Ignores unsuppored styles', -> - @component.handleKeyCommand('underline') - @component.state.html.should.eql 'Here is the lead paragraph for this article.
' - - it 'Can override default styles via props.styleMap', -> - @props.styleMap = ['bold'] - @props.text = 'Here is the a paragraph for this article.
' - component = ReactDOM.render React.createElement(@RichTextParagraph, @props), (@$el = $ "")[0] - component.state.editorState.getSelection().isCollapsed = sinon.stub().returns false - selection = component.state.editorState.getSelection() - newSelection = selection.merge({ - anchorKey: component.state.editorState.getCurrentContent().getFirstBlock().key - anchorOffset: 0 - focusKey: component.state.editorState.getCurrentContent().getFirstBlock().key - focusOffset: 7 - }) - newEditorState = @d.EditorState.acceptSelection(component.state.editorState, newSelection) - component.onChange newEditorState - - component.handleKeyCommand('italic') - component.state.html.should.eql 'Here is the a paragraph for this article.
' - - component.handleKeyCommand('bold') - component.state.html.should.eql 'Here is the a paragraph for this article.
' - - it 'Strips unsuported html out of pasted text', -> - @component.onPaste 'Available at: Espacio Valverde Galleries Sector, Booth 9F01', 'Available at: Espacio Valverde • Galleries Sector, Booth 9F01 the lead paragraph for this article.
' - - it 'Sends converted html to parent onChange', -> - @component.onChange @component.state.editorState - @component.props.onChange.called.should.eql true - @component.props.onChange.args[0][0].should.eql 'Here is the lead paragraph for this article.
' diff --git a/client/components/rich_text2/test/edit_nav.test.coffee b/client/components/rich_text2/test/nav.test.coffee similarity index 89% rename from client/components/rich_text2/test/edit_nav.test.coffee rename to client/components/rich_text2/test/nav.test.coffee index 4913d34e9..0766c8b79 100644 --- a/client/components/rich_text2/test/edit_nav.test.coffee +++ b/client/components/rich_text2/test/nav.test.coffee @@ -13,14 +13,14 @@ r = { blockTypes, inlineStyles } = require '../../../apps/edit/components/content2/sections/text/draft_config.coffee' -describe 'RichText: EditNav', -> +describe 'RichText: Nav', -> describe 'Classic: partner', -> beforeEach (done) -> benv.setup => benv.expose $: benv.require 'jquery' - @EditNav = benv.requireWithJadeify( - resolve(__dirname, '../components/edit_nav'), ['icons'] + @Nav = benv.requireWithJadeify( + resolve(__dirname, '../components/nav'), ['icons'] ) @props = { hasFeatures: false @@ -32,7 +32,7 @@ describe 'RichText: EditNav', -> makePlainText: @makePlainText = sinon.stub() position: { top: 20, left: 20 } } - @component = ReactDOM.render React.createElement(@EditNav, @props), (@$el = $ "")[0], => setTimeout => + @component = ReactDOM.render React.createElement(@Nav, @props), (@$el = $ "")[0], => setTimeout => done() afterEach -> @@ -81,8 +81,8 @@ describe 'RichText: EditNav', -> beforeEach (done) -> benv.setup => benv.expose $: benv.require 'jquery' - @EditNav = benv.requireWithJadeify( - resolve(__dirname, '../components/edit_nav'), ['icons'] + @Nav = benv.requireWithJadeify( + resolve(__dirname, '../components/nav'), ['icons'] ) @props = { hasFeatures: true @@ -94,7 +94,7 @@ describe 'RichText: EditNav', -> makePlainText: @makePlainText = sinon.stub() position: { top: 20, left: 20 } } - @component = ReactDOM.render React.createElement(@EditNav, @props), (@$el = $ "")[0], => setTimeout => + @component = ReactDOM.render React.createElement(@Nav, @props), (@$el = $ "")[0], => setTimeout => done() afterEach -> @@ -128,8 +128,8 @@ describe 'RichText: EditNav', -> beforeEach (done) -> benv.setup => benv.expose $: benv.require 'jquery' - @EditNav = benv.requireWithJadeify( - resolve(__dirname, '../components/edit_nav'), ['icons'] + @Nav = benv.requireWithJadeify( + resolve(__dirname, '../components/nav'), ['icons'] ) @props = { hasFeatures: true @@ -141,7 +141,7 @@ describe 'RichText: EditNav', -> makePlainText: @makePlainText = sinon.stub() position: { top: 20, left: 20 } } - @component = ReactDOM.render React.createElement(@EditNav, @props), (@$el = $ "")[0], => setTimeout => + @component = ReactDOM.render React.createElement(@Nav, @props), (@$el = $ "")[0], => setTimeout => done() afterEach -> @@ -176,8 +176,8 @@ describe 'RichText: EditNav', -> beforeEach (done) -> benv.setup => benv.expose $: benv.require 'jquery' - @EditNav = benv.requireWithJadeify( - resolve(__dirname, '../components/edit_nav'), ['icons'] + @Nav = benv.requireWithJadeify( + resolve(__dirname, '../components/nav'), ['icons'] ) @props = { hasFeatures: true @@ -189,7 +189,7 @@ describe 'RichText: EditNav', -> makePlainText: @makePlainText = sinon.stub() position: { top: 20, left: 20 } } - @component = ReactDOM.render React.createElement(@EditNav, @props), (@$el = $ "")[0], => setTimeout => + @component = ReactDOM.render React.createElement(@Nav, @props), (@$el = $ "")[0], => setTimeout => done() afterEach -> diff --git a/client/components/rich_text2/test/paragraph.test.coffee b/client/components/rich_text2/test/paragraph.test.coffee new file mode 100644 index 000000000..528e2c754 --- /dev/null +++ b/client/components/rich_text2/test/paragraph.test.coffee @@ -0,0 +1,215 @@ +benv = require 'benv' +{ resolve } = require 'path' +sinon = require 'sinon' +React = require 'react' +ReactDOM = require 'react-dom' +ReactTestUtils = require 'react-addons-test-utils' +ReactDOMServer = require 'react-dom/server' +Draft = require 'draft-js' +{ EditorState, Modifier } = require 'draft-js' + +r = + find: ReactTestUtils.findRenderedDOMComponentWithClass + simulate: ReactTestUtils.Simulate + +describe 'Rich Text: Paragraph', -> + + beforeEach (done) -> + benv.setup => + benv.expose + $: benv.require 'jquery' + window.jQuery = $ + global.Node = () => {} + global.HTMLElement = () => {} + global.HTMLAnchorElement = () => {} + window.getSelection = sinon.stub().returns( + isCollapsed: false + getRangeAt: sinon.stub().returns( + getClientRects: sinon.stub.returns([{ + bottom: 170 + height: 25 + left: 425 + right: 525 + top: 145 + width: 95 + }]) + ) + ) + @Paragraph = benv.require resolve __dirname, '../components/paragraph' + @Paragraph.__set__ 'Modifier', Modifier + @Paragraph.__set__ 'EditorState', EditorState + @Utils = benv.require resolve __dirname, '../utils' + @Paragraph.__set__ 'Utils', @Utils + Config = require '../utils/config.coffee' + @Paragraph.__set__ 'Config', Config + Nav = benv.requireWithJadeify( + resolve(__dirname, '../components/nav'), ['icons'] + ) + @Paragraph.__set__ 'Nav', React.createFactory Nav + InputUrl = benv.requireWithJadeify( + resolve(__dirname, '../components/input_url'), ['icons'] + ) + @Paragraph.__set__ 'InputUrl', React.createFactory InputUrl + @Paragraph.__set__ 'Utils.stickyControlsBox', sinon.stub().returns {top: 20, left: 40} + @Paragraph.__set__ 'Utils.getSelectionLocation', sinon.stub().returns({top: 40, left: 20}) + @Paragraph.__set__ 'Utils.stripGoogleStyles', @stripGoogleStyles = sinon.stub().returns('hello
here again.
') + @leadParagraph = 'Here is the lead paragraph for this article.
' + @postscript = 'Illustration by Tomi Um.
' + @props = { + html: @postscript + placeholder: 'Postscript (optional)' + onChange: sinon.stub() + linked: true + } + @component = ReactDOM.render React.createElement(@Paragraph, @props), (@$el = $ "")[0], => setTimeout => + @component.stickyControlsBox = sinon.stub().returns {top: 20, left: 40} + @component.state.editorState.getSelection().isCollapsed = sinon.stub().returns false + selection = @component.state.editorState.getSelection() + newSelection = selection.merge({ + anchorKey: @component.state.editorState.getCurrentContent().getFirstBlock().key + anchorOffset: 0 + focusKey: @component.state.editorState.getCurrentContent().getFirstBlock().key + focusOffset: 12 + }) + newEditorState = EditorState.acceptSelection(@component.state.editorState, newSelection) + @component.onChange newEditorState + done() + + afterEach -> + benv.teardown() + + describe 'Render', -> + + it 'Shows a placeholder if provided and empty', -> + component = ReactDOMServer.renderToString React.createElement(@Paragraph, @props) + $(component).text().should.containEql 'Postscript (optional)' + + it 'Renders existing html', -> + $(ReactDOM.findDOMNode(@component)).text().should.containEql 'Illustration by Tomi Um.' + + it 'Renders existing link entities', -> + $(ReactDOM.findDOMNode(@component)).html().should.containEql '' + + describe 'Key commands', -> + + it 'Can toggle bold styles', -> + @component.setState = sinon.stub() + @component.handleKeyCommand('bold') + @component.setState.args[0][0].html.should.containEql 'Illustration' + + it 'Can toggle italic styles', -> + @component.setState = sinon.stub() + @component.handleKeyCommand('italic') + @component.setState.args[0][0].html.should.containEql 'Illustration' + + it 'Can toggle a link prompt', -> + @component.setState = sinon.stub() + @component.handleKeyCommand('link-prompt') + @component.setState.args[0][0].selectionTarget.should.eql { top: 20, left: 40 } + @component.setState.args[0][0].showUrlInput.should.eql true + + describe 'Nav', -> + + it 'Prints Italic and Bold buttons by default', -> + @props.linked = null + component = ReactDOM.render React.createElement(@Paragraph, @props), (@$el = $ "")[0], => + component.setState showNav: true + $(ReactDOM.findDOMNode(component)).html().should.containEql '' + $(ReactDOM.findDOMNode(component)).html().should.not.containEql '