diff --git a/blocks/api/factory.js b/blocks/api/factory.js index 0244967f7bfe51..d6a3e5b0d7ba11 100644 --- a/blocks/api/factory.js +++ b/blocks/api/factory.js @@ -4,33 +4,22 @@ import uuid from 'uuid/v4'; import { get, castArray, findIndex, isObjectLike, find } from 'lodash'; -/** - * Internal dependencies - */ -import { getBlockType } from './registration'; - /** * Returns a block object given its type and attributes. * - * @param {String} name Block name + * @param {String} blockType Block type * @param {Object} attributes Block attributes * @return {Object} Block object */ -export function createBlock( name, attributes = {} ) { - // Get the type definition associated with a registered block. - const blockType = getBlockType( name ); - +export function createBlock( blockType, attributes = {} ) { // Do we need this? What purpose does it have? - let defaultAttributes; - if ( blockType ) { - defaultAttributes = blockType.defaultAttributes; - } + const defaultAttributes = blockType.defaultAttributes; // Blocks are stored with a unique ID, the assigned type name, // and the block attributes. return { uid: uuid(), - name, + name: blockType.name, isValid: true, attributes: { ...defaultAttributes, @@ -42,19 +31,18 @@ export function createBlock( name, attributes = {} ) { /** * Switch a block into one or more blocks of the new block type. * - * @param {Object} block Block object - * @param {string} name Block name - * @return {Array} Block object + * @param {Object} block Block object + * @param {string} sourceType Source Block type + * @param {string} destinationType Destination Block type + * @return {Array} Block object */ -export function switchToBlockType( block, name ) { +export function switchToBlockType( block, sourceType, destinationType ) { // Find the right transformation by giving priority to the "to" // transformation. - const destinationType = getBlockType( name ); - const sourceType = getBlockType( block.name ); const transformationsFrom = get( destinationType, 'transforms.from', [] ); const transformationsTo = get( sourceType, 'transforms.to', [] ); const transformation = - find( transformationsTo, t => t.blocks.indexOf( name ) !== -1 ) || + find( transformationsTo, t => t.blocks.indexOf( destinationType.name ) !== -1 ) || find( transformationsFrom, t => t.blocks.indexOf( block.name ) !== -1 ); // Stop if there is no valid transformation. (How did we get here?) @@ -74,13 +62,7 @@ export function switchToBlockType( block, name ) { // with an array instead. transformationResults = castArray( transformationResults ); - // Ensure that every block object returned by the transformation has a - // valid block type. - if ( transformationResults.some( ( result ) => ! getBlockType( result.name ) ) ) { - return null; - } - - const firstSwitchedBlock = findIndex( transformationResults, ( result ) => result.name === name ); + const firstSwitchedBlock = findIndex( transformationResults, ( result ) => result.name === destinationType.name ); // Ensure that at least one block object returned by the transformation has // the expected "destination" block type. diff --git a/blocks/api/parser.js b/blocks/api/parser.js index 63af6229e39912..6f5bc4582851c3 100644 --- a/blocks/api/parser.js +++ b/blocks/api/parser.js @@ -2,13 +2,12 @@ * External dependencies */ import { parse as hpqParse } from 'hpq'; -import { pickBy } from 'lodash'; +import { pickBy, find } from 'lodash'; /** * Internal dependencies */ import { parse as grammarParse } from './post.pegjs'; -import { getBlockType, getUnknownTypeHandler } from './registration'; import { createBlock } from './factory'; import { getBeautifulContent, getSaveContent } from './serializer'; @@ -63,38 +62,30 @@ export function getBlockAttributes( blockType, rawContent, attributes ) { /** * Creates a block with fallback to the unknown type handler. * - * @param {?String} name Block type name - * @param {String} rawContent Raw block content - * @param {?Object} attributes Attributes obtained from block delimiters - * @return {?Object} An initialized block object (if possible) + * @param {Object} blockType Block type name + * @param {Object} fallbackBlockType Fallback Block type name + * @param {String} rawContent Raw block content + * @param {?Object} attributes Attributes obtained from block delimiters + * @return {?Object} An initialized block object (if possible) */ -export function createBlockWithFallback( name, rawContent, attributes ) { - // Use type from block content, otherwise find unknown handler. - name = name || getUnknownTypeHandler(); - - // Try finding type for known block name, else fall back again. - let blockType = getBlockType( name ); - const fallbackBlock = getUnknownTypeHandler(); - if ( ! blockType ) { - name = fallbackBlock; - blockType = getBlockType( name ); - } +export function createBlockWithFallback( blockType, fallbackBlockType, rawContent, attributes ) { + const parsedBlockType = blockType || fallbackBlockType; // Include in set only if type were determined. // TODO do we ever expect there to not be an unknown type handler? - if ( blockType && ( rawContent || name !== fallbackBlock ) ) { + if ( parsedBlockType && ( rawContent || parsedBlockType !== fallbackBlockType ) ) { // TODO allow blocks to opt-in to receiving a tree instead of a string. // Gradually convert all blocks to this new format, then remove the // string serialization. const block = createBlock( - name, - getBlockAttributes( blockType, rawContent, attributes ) + parsedBlockType, + getBlockAttributes( parsedBlockType, rawContent, attributes ) ); // Validate that the parsed block is valid, meaning that if we were to // reserialize it given the assumed attributes, the markup matches the // original value. Otherwise, preserve original to avoid destruction. - block.isValid = isValidBlock( rawContent, blockType, block.attributes ); + block.isValid = isValidBlock( rawContent, parsedBlockType, block.attributes ); if ( ! block.isValid ) { block.originalContent = rawContent; } @@ -138,13 +129,18 @@ export function isValidBlock( rawContent, blockType, attributes ) { /** * Parses the post content with a PegJS grammar and returns a list of blocks. * - * @param {String} content The post content - * @return {Array} Block list + * @param {String} content The post content + * @param {Array} blockTypes Block Types + * @param {String} fallbackBlockName Fallback Block Name + * @return {Array} Block list */ -export function parseWithGrammar( content ) { +export function parseWithGrammar( content, blockTypes, fallbackBlockName ) { + const getBlockType = ( name ) => find( blockTypes, ( bt ) => bt.name === name ); + const fallbackBlockType = getBlockType( fallbackBlockName ); return grammarParse( content ).reduce( ( memo, blockNode ) => { const { blockName, rawContent, attrs } = blockNode; - const block = createBlockWithFallback( blockName, rawContent.trim(), attrs ); + const blockType = getBlockType( blockName ); + const block = createBlockWithFallback( blockType, fallbackBlockType, rawContent.trim(), attrs ); if ( block ) { memo.push( block ); } diff --git a/blocks/api/paste.js b/blocks/api/paste.js index 282729ac18b573..12770b8e7fd023 100644 --- a/blocks/api/paste.js +++ b/blocks/api/paste.js @@ -70,8 +70,10 @@ export function normaliseToBlockLevelNodes( nodes ) { } export default function( nodes ) { + const blockTypes = getBlockTypes(); + const unknownTypeBlockType = find( blockTypes, ( bt ) => bt.name === getUnknownTypeHandler() ); return normaliseToBlockLevelNodes( nodes ).map( ( node ) => { - const block = getBlockTypes().reduce( ( acc, blockType ) => { + const block = blockTypes.reduce( ( acc, blockType ) => { if ( acc ) { return acc; } @@ -83,17 +85,17 @@ export default function( nodes ) { return acc; } - const { name, defaultAttributes = [] } = blockType; + const { defaultAttributes = [] } = blockType; const attributes = parseBlockAttributes( node.outerHTML, transform.attributes ); - return createBlock( name, { ...defaultAttributes, ...attributes } ); + return createBlock( blockType, { ...defaultAttributes, ...attributes } ); }, null ); if ( block ) { return block; } - return createBlock( getUnknownTypeHandler(), { + return createBlock( unknownTypeBlockType, { content: node.outerHTML, } ); } ); diff --git a/blocks/api/serializer.js b/blocks/api/serializer.js index 9c56e2b703f2e1..0718f01be70de3 100644 --- a/blocks/api/serializer.js +++ b/blocks/api/serializer.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { isEmpty, reduce, isObject } from 'lodash'; +import { isEmpty, reduce, isObject, find } from 'lodash'; import { html as beautifyHtml } from 'js-beautify'; import classnames from 'classnames'; @@ -13,7 +13,6 @@ import { Component, createElement, renderToString, cloneElement, Children } from /** * Internal dependencies */ -import { getBlockType } from './registration'; import { parseBlockAttributes } from './parser'; /** @@ -125,9 +124,8 @@ export function getBeautifulContent( content ) { } ); } -export function serializeBlock( block ) { +export function serializeBlock( block, blockType ) { const blockName = block.name; - const blockType = getBlockType( blockName ); let saveContent; if ( block.isValid ) { @@ -162,9 +160,15 @@ export function serializeBlock( block ) { /** * Takes a block list and returns the serialized post content. * - * @param {Array} blocks Block list - * @return {String} The post content + * @param {Array} blocks Block list + * @param {Array} blockTypes Block Types + * @return {String} The post content */ -export default function serialize( blocks ) { - return blocks.map( serializeBlock ).join( '\n\n' ); +export default function serialize( blocks, blockTypes ) { + const getBlockType = ( name ) => find( blockTypes, ( bt ) => bt.name === name ); + return blocks + .map( ( block ) => + serializeBlock( block, getBlockType( block.name ) ) + ) + .join( '\n\n' ); } diff --git a/blocks/editable/index.js b/blocks/editable/index.js index ab5230a1cd1d43..2e6c01c6aefc02 100644 --- a/blocks/editable/index.js +++ b/blocks/editable/index.js @@ -12,7 +12,7 @@ import 'element-closest'; * WordPress dependencies */ import { createElement, Component, renderToString } from 'element'; -import { parse, pasteHandler } from '../api'; +import { parse, pasteHandler, getBlockTypes, getUnknownTypeHandler } from '../api'; import { BACKSPACE, DELETE, ENTER } from 'utils/keycodes'; /** @@ -145,7 +145,7 @@ export default class Editable extends Component { // Internal paste, so parse. if ( childNodes.some( isBlockDelimiter ) ) { - blocks = parse( event.node.innerHTML.replace( /]+>/, '' ) ); + blocks = parse( getBlockTypes(), getUnknownTypeHandler(), event.node.innerHTML.replace( /]+>/, '' ) ); // External paste with block level content, so attempt to assign // blocks. } else if ( childNodes.some( isBlockPart ) ) { diff --git a/blocks/library/heading/index.js b/blocks/library/heading/index.js index b2bd04a4f5c690..5fe50c4f2995f6 100644 --- a/blocks/library/heading/index.js +++ b/blocks/library/heading/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { isObject } from 'lodash'; +import { isObject, find } from 'lodash'; /** * WordPress dependencies @@ -113,8 +113,9 @@ registerBlockType( 'core/heading', { }; }, - edit( { attributes, setAttributes, focus, setFocus, mergeBlocks, insertBlocksAfter } ) { + edit( { attributes, setAttributes, focus, setFocus, mergeBlocks, insertBlocksAfter, editorConfig } ) { const { align, content, nodeName, placeholder } = attributes; + const getBlockType = ( name ) => find( editorConfig.blockTypes, ( bt ) => bt.name === name ); return [ focus && ( @@ -170,7 +171,7 @@ registerBlockType( 'core/heading', { setAttributes( { content: before } ); insertBlocksAfter( [ ...blocks, - createBlock( 'core/text', { content: after } ), + createBlock( getBlockType( 'core/text' ), { content: after } ), ] ); } } style={ { textAlign: align } } diff --git a/blocks/library/text/index.js b/blocks/library/text/index.js index 14b91331d218fd..0d3ce149beacff 100644 --- a/blocks/library/text/index.js +++ b/blocks/library/text/index.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import { find } from 'lodash'; + /** * WordPress dependencies */ @@ -53,9 +58,10 @@ registerBlockType( 'core/text', { }; }, - edit( { attributes, setAttributes, insertBlocksAfter, focus, setFocus, mergeBlocks, onReplace } ) { + edit( { attributes, setAttributes, insertBlocksAfter, focus, setFocus, mergeBlocks, onReplace, editorConfig } ) { const { align, content, dropCap, placeholder } = attributes; const toggleDropCap = () => setAttributes( { dropCap: ! dropCap } ); + const getBlockType = ( name ) => find( editorConfig.blockTypes, ( bt ) => bt.name === name ); return [ focus && ( @@ -95,7 +101,7 @@ registerBlockType( 'core/text', { setAttributes( { content: before } ); insertBlocksAfter( [ ...blocks, - createBlock( 'core/text', { content: after } ), + createBlock( getBlockType( 'core/text' ), { content: after } ), ] ); } } onMerge={ mergeBlocks } diff --git a/editor/README.md b/editor-chrome/README.md similarity index 100% rename from editor/README.md rename to editor-chrome/README.md diff --git a/editor-chrome/actions.js b/editor-chrome/actions.js new file mode 100644 index 00000000000000..ef4d292e640ef7 --- /dev/null +++ b/editor-chrome/actions.js @@ -0,0 +1,87 @@ +/** + * External Dependencies + */ +import uuid from 'uuid/v4'; +import { partial } from 'lodash'; + +export function editPost( edits ) { + return { + type: 'EDIT_POST', + edits, + }; +} + +export function savePost() { + return { + type: 'REQUEST_POST_UPDATE', + }; +} + +export function trashPost( postId, postType ) { + return { + type: 'TRASH_POST', + postId, + postType, + }; +} + +/** + * Returns an action object used in signalling that the post should autosave. + * + * @return {Object} Action object + */ +export function autosave() { + return { + type: 'AUTOSAVE', + }; +} + +/** + * Returns an action object used in signalling that the post should be queued + * for autosave after a delay. + * + * @return {Object} Action object + */ +export function queueAutosave() { + return { + type: 'QUEUE_AUTOSAVE', + }; +} + +/** + * Returns an action object used to create a notice + * + * @param {String} status The notice status + * @param {WPElement} content The notice content + * @param {String} id The notice id + * + * @return {Object} Action object + */ +export function createNotice( status, content, id = uuid() ) { + return { + type: 'CREATE_NOTICE', + notice: { + id, + status, + content, + }, + }; +} + +/** + * Returns an action object used to remove a notice + * + * @param {String} id The notice id + * + * @return {Object} Action object + */ +export function removeNotice( id ) { + return { + type: 'REMOVE_NOTICE', + noticeId: id, + }; +} + +export const createSuccessNotice = partial( createNotice, 'success' ); +export const createErrorNotice = partial( createNotice, 'error' ); +export const createWarningNotice = partial( createNotice, 'warning' ); diff --git a/editor/assets/stylesheets/_animations.scss b/editor-chrome/assets/stylesheets/_animations.scss similarity index 100% rename from editor/assets/stylesheets/_animations.scss rename to editor-chrome/assets/stylesheets/_animations.scss diff --git a/editor/assets/stylesheets/_mixins.scss b/editor-chrome/assets/stylesheets/_mixins.scss similarity index 100% rename from editor/assets/stylesheets/_mixins.scss rename to editor-chrome/assets/stylesheets/_mixins.scss diff --git a/editor/assets/stylesheets/_variables.scss b/editor-chrome/assets/stylesheets/_variables.scss similarity index 100% rename from editor/assets/stylesheets/_variables.scss rename to editor-chrome/assets/stylesheets/_variables.scss diff --git a/editor/assets/stylesheets/_z-index.scss b/editor-chrome/assets/stylesheets/_z-index.scss similarity index 100% rename from editor/assets/stylesheets/_z-index.scss rename to editor-chrome/assets/stylesheets/_z-index.scss diff --git a/editor/assets/stylesheets/main.scss b/editor-chrome/assets/stylesheets/main.scss similarity index 100% rename from editor/assets/stylesheets/main.scss rename to editor-chrome/assets/stylesheets/main.scss diff --git a/editor/document-title/index.js b/editor-chrome/document-title/index.js similarity index 100% rename from editor/document-title/index.js rename to editor-chrome/document-title/index.js diff --git a/editor/effects.js b/editor-chrome/effects.js similarity index 78% rename from editor/effects.js rename to editor-chrome/effects.js index 5b665b13a19f5a..2a2ca991120697 100644 --- a/editor/effects.js +++ b/editor-chrome/effects.js @@ -7,7 +7,6 @@ import { get, uniqueId, debounce } from 'lodash'; /** * WordPress dependencies */ -import { serialize, getBlockType, switchToBlockType } from 'blocks'; import { __ } from 'i18n'; /** @@ -15,8 +14,6 @@ import { __ } from 'i18n'; */ import { getGutenbergURL, getWPAdminURL } from './utils/url'; import { - focusBlock, - replaceBlocks, createSuccessNotice, createErrorNotice, autosave, @@ -27,7 +24,6 @@ import { import { getCurrentPost, getCurrentPostType, - getBlocks, getPostEdits, isCurrentPostPublished, isEditedPostDirty, @@ -43,7 +39,6 @@ export default { const edits = getPostEdits( state ); const toSend = { ...edits, - content: serialize( getBlocks( state ) ), id: post.id, }; const transactionId = uniqueId(); @@ -177,49 +172,6 @@ export default { const message = action.error.message && action.error.code !== 'unknown_error' ? action.error.message : __( 'Trashing failed' ); store.dispatch( createErrorNotice( message ) ); }, - MERGE_BLOCKS( action, store ) { - const { dispatch } = store; - const [ blockA, blockB ] = action.blocks; - const blockType = getBlockType( blockA.name ); - - // Only focus the previous block if it's not mergeable - if ( ! blockType.merge ) { - dispatch( focusBlock( blockA.uid ) ); - return; - } - - // We can only merge blocks with similar types - // thus, we transform the block to merge first - const blocksWithTheSameType = blockA.name === blockB.name - ? [ blockB ] - : switchToBlockType( blockB, blockA.name ); - - // If the block types can not match, do nothing - if ( ! blocksWithTheSameType || ! blocksWithTheSameType.length ) { - return; - } - - // Calling the merge to update the attributes and remove the block to be merged - const updatedAttributes = blockType.merge( - blockA.attributes, - blocksWithTheSameType[ 0 ].attributes - ); - - dispatch( focusBlock( blockA.uid, { offset: -1 } ) ); - dispatch( replaceBlocks( - [ blockA.uid, blockB.uid ], - [ - { - ...blockA, - attributes: { - ...blockA.attributes, - ...updatedAttributes, - }, - }, - ...blocksWithTheSameType.slice( 1 ), - ] - ) ); - }, AUTOSAVE( action, store ) { const { getState, dispatch } = store; const state = getState(); @@ -246,12 +198,6 @@ export default { QUEUE_AUTOSAVE: debounce( ( action, store ) => { store.dispatch( autosave() ); }, 10000 ), - UPDATE_BLOCK_ATTRIBUTES: () => queueAutosave(), - INSERT_BLOCKS: () => queueAutosave(), - MOVE_BLOCKS_DOWN: () => queueAutosave(), - MOVE_BLOCKS_UP: () => queueAutosave(), - REPLACE_BLOCKS: () => queueAutosave(), - REMOVE_BLOCKS: () => queueAutosave(), EDIT_POST: () => queueAutosave(), MARK_DIRTY: () => queueAutosave(), }; diff --git a/editor-chrome/header/index.js b/editor-chrome/header/index.js new file mode 100644 index 00000000000000..b6f9f23e285536 --- /dev/null +++ b/editor-chrome/header/index.js @@ -0,0 +1,25 @@ +/** + * WordPress dependencies + */ +import { MultiSelectionHeader } from 'editor'; + +/** + * Internal dependencies + */ +import './style.scss'; +import ModeSwitcher from './mode-switcher'; +import SavedState from './saved-state'; +import Tools from './tools'; + +function Header() { + return ( +
+ + + + +
+ ); +} + +export default Header; diff --git a/editor/header/mode-switcher/index.js b/editor-chrome/header/mode-switcher/index.js similarity index 100% rename from editor/header/mode-switcher/index.js rename to editor-chrome/header/mode-switcher/index.js diff --git a/editor/header/mode-switcher/style.scss b/editor-chrome/header/mode-switcher/style.scss similarity index 100% rename from editor/header/mode-switcher/style.scss rename to editor-chrome/header/mode-switcher/style.scss diff --git a/editor/header/saved-state/index.js b/editor-chrome/header/saved-state/index.js similarity index 100% rename from editor/header/saved-state/index.js rename to editor-chrome/header/saved-state/index.js diff --git a/editor/header/saved-state/style.scss b/editor-chrome/header/saved-state/style.scss similarity index 100% rename from editor/header/saved-state/style.scss rename to editor-chrome/header/saved-state/style.scss diff --git a/editor/header/saved-state/test/index.js b/editor-chrome/header/saved-state/test/index.js similarity index 100% rename from editor/header/saved-state/test/index.js rename to editor-chrome/header/saved-state/test/index.js diff --git a/editor/header/style.scss b/editor-chrome/header/style.scss similarity index 83% rename from editor/header/style.scss rename to editor-chrome/header/style.scss index 6251ba5dae434b..5726c439da202c 100644 --- a/editor/header/style.scss +++ b/editor-chrome/header/style.scss @@ -67,18 +67,3 @@ left: -$admin-sidebar-width-big; right: $admin-sidebar-width-big; } - -.editor-header-multi-select { - background: $blue-medium-100; - border-bottom: 1px solid $blue-medium-200; -} - -.editor-selected-count { - padding-right: $item-spacing; - color: $dark-gray-500; - border-right: 1px solid $light-gray-500; -} - -.editor-selected-clear { - margin: 0 0 0 auto; -} diff --git a/editor/header/tools/index.js b/editor-chrome/header/tools/index.js similarity index 97% rename from editor/header/tools/index.js rename to editor-chrome/header/tools/index.js index 2fc32fd7b17167..f4e00e1825209c 100644 --- a/editor/header/tools/index.js +++ b/editor-chrome/header/tools/index.js @@ -8,12 +8,12 @@ import { connect } from 'react-redux'; */ import { __ } from 'i18n'; import { IconButton } from 'components'; +import { Inserter } from 'editor'; /** * Internal dependencies */ import './style.scss'; -import Inserter from '../../inserter'; import PublishButton from './publish-button'; import PreviewButton from './preview-button'; import { isEditorSidebarOpened, hasEditorUndo, hasEditorRedo } from '../../selectors'; diff --git a/editor/header/tools/preview-button.js b/editor-chrome/header/tools/preview-button.js similarity index 100% rename from editor/header/tools/preview-button.js rename to editor-chrome/header/tools/preview-button.js diff --git a/editor/header/tools/publish-button.js b/editor-chrome/header/tools/publish-button.js similarity index 100% rename from editor/header/tools/publish-button.js rename to editor-chrome/header/tools/publish-button.js diff --git a/editor/header/tools/style.scss b/editor-chrome/header/tools/style.scss similarity index 100% rename from editor/header/tools/style.scss rename to editor-chrome/header/tools/style.scss diff --git a/editor-chrome/index.js b/editor-chrome/index.js new file mode 100644 index 00000000000000..c1a7ac691a5aa2 --- /dev/null +++ b/editor-chrome/index.js @@ -0,0 +1,91 @@ +/** + * External dependencies + */ +import { Provider as ReduxProvider } from 'react-redux'; +import { Provider as SlotFillProvider } from 'react-slot-fill'; +import moment from 'moment-timezone'; +import 'moment-timezone/moment-timezone-utils'; + +/** + * WordPress dependencies + */ +import { parse, getBlockTypes, getCategories, getDefaultBlock, getUnknownTypeHandler } from 'blocks'; +import { render } from 'element'; +import { settings } from 'date'; + +/** + * Internal dependencies + */ +import './assets/stylesheets/main.scss'; +import Layout from './layout'; +import { createReduxStore } from './state'; + +// Configure moment globally +moment.locale( settings.l10n.locale ); +if ( settings.timezone.string ) { + moment.tz.setDefault( settings.timezone.string ); +} else { + const momentTimezone = { + name: 'WP', + abbrs: [ 'WP' ], + untils: [ null ], + offsets: [ -settings.timezone.offset * 60 ], + }; + const unpackedTimezone = moment.tz.pack( momentTimezone ); + moment.tz.add( unpackedTimezone ); + moment.tz.setDefault( 'WP' ); +} + +/** + * Initializes Redux state with bootstrapped post, if provided. + * + * @param {Redux.Store} store Redux store instance + * @param {Object} post Bootstrapped post object + */ +function preparePostState( store, post ) { + // Set current post into state + store.dispatch( { + type: 'RESET_POST', + post, + } ); + + // Include auto draft title in edits while not flagging post as dirty + if ( post.status === 'auto-draft' ) { + store.dispatch( { + type: 'SETUP_NEW_POST', + edits: { + title: post.title.raw, + }, + } ); + } +} + +/** + * Initializes and returns an instance of Editor. + * + * @param {String} id Unique identifier for editor instance + * @param {Object} post API entity for post to edit (type required) + */ +export function createEditorInstance( id, post ) { + const editorConfig = { + blockTypes: getBlockTypes(), + categories: getCategories(), + defaultBlockType: getDefaultBlock(), + fallbackBlockType: getUnknownTypeHandler(), + }; + + const store = createReduxStore(); + store.dispatch( { + type: 'LOAD_USER_DATA', + } ); + preparePostState( store, post ); + + render( + + + + + , + document.getElementById( id ) + ); +} diff --git a/editor-chrome/layout/index.js b/editor-chrome/layout/index.js new file mode 100644 index 00000000000000..11d28a7d42030f --- /dev/null +++ b/editor-chrome/layout/index.js @@ -0,0 +1,105 @@ +/** + * External dependencies + */ +import { connect } from 'react-redux'; +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { NoticeList } from 'components'; +import { Provider as EditorProvider } from 'editor'; +import { parse, serialize } from 'blocks'; +import { Component } from 'element'; + +/** + * Internal dependencies + */ +import './style.scss'; +import Header from '../header'; +import Sidebar from '../sidebar'; +import TextEditor from '../modes/text-editor'; +import VisualEditor from '../modes/visual-editor'; +import UnsavedChangesWarning from '../unsaved-changes-warning'; +import DocumentTitle from '../document-title'; +import { removeNotice, editPost } from '../actions'; +import { + getEditorMode, + isEditorSidebarOpened, + getNotices, + getEditedPostContent, +} from '../selectors'; + +class Layout extends Component { + constructor() { + super( ...arguments ); + this.onChangeBlocks = this.onChangeBlocks.bind( this ); + this.onChangeHtml = this.onChangeHtml.bind( this ); + this.state = { + blocks: [], + html: '', + }; + } + + onChangeBlocks( blocks ) { + const html = serialize( blocks, this.props.config.blockTypes ); + this.setState( { + blocks, + html, + } ); + this.props.editPost( { content: html } ); + } + + onChangeHtml( html ) { + const { blockTypes, fallbackBlockType } = this.props.config; + const blocks = parse( html, blockTypes, fallbackBlockType ); + console.log( html, blocks ); + this.setState( { + blocks, + html, + } ); + this.props.editPost( { content: html } ); + } + + componentDidMount() { + const { blockTypes, fallbackBlockType } = this.props.config; + this.setState( { + html: this.props.value, + blocks: parse( this.props.value, blockTypes, fallbackBlockType ), + } ); + } + + render() { + const { config, mode, isSidebarOpened, notices, ...props } = this.props; + const className = classnames( 'editor-layout', { + 'is-sidebar-opened': isSidebarOpened, + } ); + const { blocks, html } = this.state; + + return ( + +
+ + + +
+
+ { mode === 'text' && } + { mode === 'visual' && } +
+ { isSidebarOpened && } +
+
+ ); + } +} + +export default connect( + ( state ) => ( { + mode: getEditorMode( state ), + isSidebarOpened: isEditorSidebarOpened( state ), + notices: getNotices( state ), + value: getEditedPostContent( state ), + } ), + { removeNotice, editPost } +)( Layout ); diff --git a/editor/layout/style.scss b/editor-chrome/layout/style.scss similarity index 100% rename from editor/layout/style.scss rename to editor-chrome/layout/style.scss diff --git a/editor/modes/text-editor/index.js b/editor-chrome/modes/text-editor/index.js similarity index 63% rename from editor/modes/text-editor/index.js rename to editor-chrome/modes/text-editor/index.js index e95a90f38dd174..ccbe3c70aa3ae2 100644 --- a/editor/modes/text-editor/index.js +++ b/editor-chrome/modes/text-editor/index.js @@ -8,61 +8,39 @@ import Textarea from 'react-autosize-textarea'; * WordPress dependencies */ import { Component } from 'element'; -import { serialize, parse } from 'blocks'; /** * Internal dependencies */ import './style.scss'; import PostTitle from '../../post-title'; -import { getBlocks } from '../../selectors'; class TextEditor extends Component { - constructor( { blocks } ) { + constructor( props ) { super( ...arguments ); - const value = serialize( blocks ); - this.state = { - blocks, - persistedValue: value, - value, - }; this.onChange = this.onChange.bind( this ); this.onBlur = this.onBlur.bind( this ); + this.state = { + value: props.value, + }; } - onChange( event ) { - this.setState( { - value: event.target.value, - } ); - this.props.markDirty(); + componentWillReceiveProps( newProps ) { + if ( newProps.value !== this.props.value ) { + this.setState( { value: newProps.value } ); + } } - onBlur() { - if ( this.state.value === this.state.persistedValue ) { - return; - } - const blocks = parse( this.state.value ); - this.setState( { - blocks, - } ); - this.props.onChange( blocks ); + onChange( event ) { + this.setState( { value: event.target.value } ); this.props.markDirty(); } - componentWillReceiveProps( newProps ) { - if ( newProps.blocks !== this.state.blocks ) { - const value = serialize( newProps.blocks ); - this.setState( { - blocks: newProps.blocks, - persistedValue: value, - value, - } ); - } + onBlur() { + this.props.onChange( this.state.value ); } render() { - const { value } = this.state; - return (
@@ -86,7 +64,7 @@ class TextEditor extends Component {