diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000000000..667a507d56e975 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = tab +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[package.json] +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 00000000000000..1b46617a163d24 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,52 @@ +{ + "root": true, + "extends": "wordpress", + "env": { + "browser": true + }, + "rules": { + "array-bracket-spacing": [ "error", "always" ], + "brace-style": [ "error", "1tbs" ], + "comma-spacing": "error", + "comma-style": "error", + "computed-property-spacing": [ "error", "always" ], + "dot-notation": "error", + "eol-last": "error", + "func-call-spacing": "error", + "indent": [ "error", "tab", { "SwitchCase": 1 } ], + "key-spacing": "error", + "keyword-spacing": "error", + "no-console": "error", + "no-debugger": "error", + "no-dupe-args": "error", + "no-dupe-keys": "error", + "no-duplicate-case": "error", + "no-else-return": "error", + "no-extra-semi": "error", + "no-lonely-if": "error", + "no-mixed-spaces-and-tabs": "error", + "no-multiple-empty-lines": [ "error", { "max": 1 } ], + "no-multi-spaces": "error", + "no-negated-in-lhs": "error", + "no-nested-ternary": "error", + "no-redeclare": "error", + "no-shadow": "error", + "no-unreachable": "error", + "no-use-before-define": [ "error", "nofunc" ], + "object-curly-spacing": [ "error", "always" ], + "padded-blocks": [ "error", "never" ], + "quote-props": [ "error", "as-needed", { "keywords": true } ], + "semi": "error", + "semi-spacing": "error", + "space-before-blocks": [ "error", "always" ], + "space-before-function-paren": [ "error", "never" ], + "space-in-parens": [ "error", "always" ], + "space-infix-ops": [ "error", { "int32Hint": false } ], + "space-unary-ops": [ "error", { + "overrides": { + "!": true + } + } ], + "valid-jsdoc": [ "error", { "requireReturn": false } ] + } +} diff --git a/README.md b/README.md index e8c31b87ddc8d3..81f89746f62e87 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,16 @@ Prototyping since 1440. This is the development and prototyping hub for the editor focus in core. +Gutenberg is the project name. Conversations and discussions take place in #core-editor in Slack. > The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery. — Matt Mullenweg +WordPress already supports a large amount of "blocks", but doesn't surface them very well, nor give them many rich layout options. By embracing the blocky nature, we can hopefully surface blocks that already exist, as well as attach more advanced layout options to each of them, allowing you to easily write richer posts. + ## Overview - What are little blocks made of? - Editor Technical Overview -- Mockups: https://cloudup.com/c9pKpaoDpQ4 ## Prototypes @@ -22,6 +24,181 @@ This is the development and prototyping hub for the editor focus in core. - WP Post grammar parser. ----- +## How Designers Can Contribute + +The editor we're building means to make the editing experience better for every WordPress user, by creating an interface that "makes writing rich posts effortless, and has 'blocks' to make it easy what today might take shortcodes, custom HTML, or 'mystery meat' embed discovery", to quote the kickoff goal. + +That is difficult. So your designer eyes and help is appreciated, in what capacity you'd like to contribute. + +A good place to start is having a look at the current mockups and the UI prototype. We also have a GitHub repository, where anything labelled "Design" could use thoughtful replies, mockups, animatics, sketches, doodles. + +With regards to specific changes to the design, the details & execution (like colors, borders, shadows), those are best done as minimal and specific iterations on the work that precedes it, so we can ideally compare. That doesn't preclude wild ideas, but should be considered for precise tasks like "give the pressed buttons more contrast", things in that vein. + +Grab the Sketch file so you don't have to start from scratch: + +**Download, Updated Mar. 15th.** + +## Mockups + +These mockups are all subject to change and feedback. + +**Basic Blocks** + +_Text_ + +![Text, Neutral](mockups/Text,%20Neutral.png) + +_Text, Hover_ + +![Text, Hover](mockups/Text,%20Hover.png) + +_Text, Selected_ + +![Text, Selected](mockups/Text,%20Selected.png) + +--- + +_Empty Image_ + +![Empty Image](mockups/Image,%20Empty.png) + +_Empty Image, Hover_ + +![Empty Image, Hover](mockups/Image,%20Empty,%20Hover.png) + +_Image_ + +![Image, Neutral](mockups/Image,%20Neutral.png) + +_Image, Hover_ + +![Image, Hover](mockups/Image,%20Hover.png) + +_Image, Selected_ + +![Image, Selected](mockups/Image,%20Selected.png) + +_Image, Caption_ + +![Image, Caption](mockups/Image,%20Caption.png) + +--- + +_Empty Quote_ + +![Empty Quote](mockups/Quote,%20Empty.png) + +_Empty Quote, Hover_ + +![Empty Quote, Hover](mockups/Quote,%20Empty,%20Hover.png) + +_Quote_ + +![Quote, Neutral](mockups/Quote,%20Neutral.png) + +_Quote, Hover_ + +![Quote, Hover](mockups/Quote,%20Hover.png) + +_Quote, Selected_ + +![Quote, Selected](mockups/Quote,%20Selected.png) + +_Quote, Citation_ + +![Quote, Citation](mockups/Quote,%20Citation.png) + +_Quote 2_ + +![Quote 2, Neutral](mockups/Quote%202,%20Neutral.png) + +_Quote 2, Hover_ + +![Quote 2, Hover](mockups/Quote%202,%20Hover.png) + +_Quote 2, Selected_ + +![Quote 2, Selected](mockups/Quote%202,%20Selected.png) + +--- + +_Heading_ + +![Heading, Neutral](mockups/Heading,%20Neutral.png) + +_Heading, Hover_ + +![Heading, Hover](mockups/Heading,%20Hover.png) + +_Heading, Selected_ + +![Heading, Selected](mockups/Heading,%20Selected.png) + +--- + +_Empty Embed_ + +![Empty Embed, Neutral](mockups/Empty%20Embed,%20Neutral.png) + +_Empty Embed, Hover_ + +![Empty Embed, Hover](mockups/Empty%20Embed,%20Hover.png) + +_Embed, Neutral_ + +![Embed, Neutral](mockups/Embed,%20Neutral.png) + +_Embed, Hover_ + +![Embed, Hover](mockups/Embed,%20Hover.png) + +_Embed, Selected_ + +![Embed, Selected](mockups/Embed,%20Selected.png) + +_Embed, Caption_ + +![Embed, Caption](mockups/Embed,%20Caption.png) + +--- + +_Gallery_ + +![Gallery, Neutral](mockups/Gallery,%20Neutral.png) + +_Gallery, Hover_ + +![Gallery, Hover](mockups/Gallery,%20Hover.png) + +_Gallery, Selected_ + +![Gallery, Selected](mockups/Gallery,%20Selected.png) + +_Gallery, Selected Image_ + +![Gallery, Selected Image](mockups/Gallery,%20Selected%20Image.png) + +_Gallery, Caption_ + +![Gallery, Caption](mockups/Gallery,%20Caption.png) + +--- + +_Basic UI controls_ + +![Drag and drop](mockups/Drag%20and%20drop.png) +![Insert](mockups/Insert.png) +![Newlines](mockups/Newlines.png) +![Type Switcher](mockups/Type%20Switcher.png) + +**Early Admin UI Concept** + +**Note:** This is how it _could_ look. + +![Admin UI](mockups/Admin%20UI.png) + +![Admin UI, Sidebar Open](mockups/Admin%20UI,%20Sidebar%20Open.png) + +**Early Mobile UI Concept** -Conversations and discussions take place in #core-editor in Slack. +![Mobile](mockups/Mobile.png) diff --git a/blocks.js b/blocks.js index 8873400246e0d2..e5bb7451da1bac 100644 --- a/blocks.js +++ b/blocks.js @@ -40,50 +40,181 @@ var config = { 'default': [] }, blockCategories: [ - { id: 'frequent', label: 'Frequently Used' }, - { id: 'media', label: 'Media' } + { id: 'common', label: 'Common' }, + { id: 'media', label: 'Media' }, + { id: 'embeds', label: 'Embeds' }, + { id: 'other', label: 'Other' }, + { id: 'layout', label: 'Layout' } ], blocks: [ - { - label: 'Paragraph', - icon: '', - categories: [ 'frequent' ] - }, - { - label: 'Heading', - icon: '', - categories: [ 'frequent' ] - }, - { - label: 'Image', - icon: '', - categories: [ 'frequent' ] - }, - { - label: 'Quote', - icon: '', - categories: [ 'frequent' ] - }, - { - label: 'Gallery', - icon: '', - categories: [ 'media' ] - }, - { - label: 'Unordered List', - icon: '', - categories: [ 'frequent' ] - }, - { - label: 'Ordered List', - icon: '', - categories: [ 'frequent' ] - }, - { - label: 'Embed', - icon: '', - categories: [ 'media' ] - } + { + id: 'paragraph', + label: 'Paragraph', + icon: '', + category: 'common' + }, + { + id: 'heading', + label: 'Heading', + icon: '', + category: 'common' + }, + { + id: 'image', + label: 'Image', + icon: '', + category: 'common' + }, + { + id: 'quote', + label: 'Quote', + icon: '', + category: 'common' + }, + { + id: 'gallery', + label: 'Gallery', + icon: '', + category: 'media' + }, + { + id: 'unordered-list', + label: 'Unordered List', + icon: '', + category: 'common' + }, + { + id: 'ordered-list', + label: 'Ordered List', + icon: '', + category: 'common' + }, + { + id: 'embed', + label: 'Embed', + icon: '', + category: 'embeds' + }, + { + id: 'separator', + label: 'Separator', + icon: '', + category: 'common' + }, + { + id: 'map', + label: 'Map', + icon: '', + category: 'embeds' + }, + { + id: 'google-map', + label: 'Google Map', + icon: '', + category: 'other' + }, + { + id: 'openstreet-map', + label: 'OpenStreet Map', + icon: '', + category: 'other' + }, + { + id: 'tweet', + label: 'Tweet', + icon: '', + category: 'other' + }, + { + id: 'video', + label: 'Video', + icon: '', + category: 'media' + }, + { + id: 'youtube', + label: 'YouTube', + icon: '', + category: 'media' + }, + { + id: 'vimeo', + label: 'Vimeo', + icon: '', + category: 'media' + }, + { + id: 'audio', + label: 'Audio', + icon: '', + category: 'media' + }, + { + id: 'form', + label: 'Form', + icon: '', + category: 'other' + }, + { + id: 'survey', + label: 'Survey', + icon: '', + category: 'other' + }, + { + id: 'toc', + label: 'Table of Contents', + icon: '', + category: 'layout' + }, + { + id: 'wordpress-post', + label: 'WordPress Post', + icon: '', + category: 'other' + }, + { + id: 'facebook-post', + label: 'Facebook Post', + icon: '', + category: 'other' + }, + { + id: 'opengraph-link', + label: 'OpenGraph Link', + icon: '', + category: 'other' + }, + { + id: 'playlist', + label: 'Playlist', + icon: '', + category: 'media' + }, + { + id: 'spotify-playlist', + label: 'Spotify Playlist', + icon: '', + category: 'media' + }, + { + id: 'poet', + label: 'Poet', + icon: '', + category: 'layout' + }, + { + id: 'custom-field', + label: 'Custom Field', + icon: '', + category: 'layout' + }, + { + id: 'gist', + label: 'Gist', + icon: '', + category: 'other' + } ] }; @@ -91,8 +222,7 @@ var editor = queryFirst( '.editor' ); var switcher = queryFirst( '.block-switcher' ); var switcherButtons = query( '.block-switcher .type svg' ); var switcherMenu = queryFirst( '.switch-block__menu' ); -var blockControls = queryFirst( '.block-controls' ); -var inlineControls = queryFirst( '.inline-controls' ); +var dockedControls = queryFirst( '.docked-controls' ); var insertBlockButton = queryFirst( '.insert-block__button' ); var insertBlockMenu = queryFirst( '.insert-block__menu' ); var insertBlockMenuSearchInput = queryFirst( '.insert-block__search' ); @@ -105,8 +235,30 @@ var imageAlignNone = queryFirst( '.block-image__no-align' ); var imageAlignLeft = queryFirst( '.block-image__align-left' ); var imageAlignRight = queryFirst( '.block-image__align-right' ); +// Contants +var KEY_ENTER = 13; +var KEY_ARROW_LEFT = 37; +var KEY_ARROW_UP = 38; +var KEY_ARROW_RIGHT = 39; +var KEY_ARROW_DOWN = 40; + +// Editor Variables var selectedBlock = null; + +// Block Menu Variables +var previouslyFocusedBlock = null; var searchBlockFilter = ''; +var blockMenuOpened = false; +var menuSelectedBlock = null; + +// Helper variables +var orderedBlocks = config.blockCategories.reduce( function( memo, category ) { + var categoryBlocks = config.blocks.filter( function( block ) { + return block.category === category.id; + } ); + + return memo.concat( categoryBlocks ); +}, [] ); var supportedBlockTags = Object.keys( config.tagTypes ) .slice( 0, -1 ) // remove 'default' option @@ -122,12 +274,12 @@ insertBlockButton.addEventListener( 'click', openBlockMenu, false ); insertBlockMenu.addEventListener( 'click', function( event ) { event.stopPropagation(); }, false ); -window.addEventListener( 'mouseup', onSelectText, false ); attachBlockHandlers(); attachControlActions(); attachTypeSwitcherActions(); attachBlockMenuSearch(); +attachKeyboardShortcuts(); /** * Core logic @@ -144,6 +296,14 @@ function getBlocks() { supportedBlockTags.map( query ) ); } +function getFocusedBlock() { + var focusedBlocks = getBlocks().filter( function( block ) { + return block.contains( window.getSelection().anchorNode ); + } ); + + return focusedBlocks.length ? focusedBlocks[ 0 ] : null; +} + function selectBlock( event ) { clearBlocks(); event.stopPropagation(); @@ -168,6 +328,7 @@ function showControls( node ) { switcherButtons.forEach( function( element ) { element.style.display = 'none'; } ); + var blockType = getTagType( node.nodeName ); var switcherQuery = '.type-icon-' + blockType; queryFirst( switcherQuery ).style.display = 'block'; @@ -178,52 +339,48 @@ function showControls( node ) { switcher.style.top = ( position.top + 18 + window.scrollY ) + 'px'; // show/hide block-specific block controls - blockControls.className = 'block-controls'; + dockedControls.className = 'docked-controls'; getTypeKinds( blockType ).forEach( function( kind ) { - blockControls.classList.add( 'is-' + kind ); + dockedControls.classList.add( 'is-' + kind ); } ); - blockControls.style.display = 'block'; + dockedControls.style.display = 'block'; // reposition block-specific block controls - blockControls.style.top = ( position.top - 36 + window.scrollY ) + 'px'; - blockControls.style.maxHeight = 'none'; + updateDockedControlsPosition(); } -function hideControls() { - switcher.style.opacity = 0; - switcherMenu.style.display = 'none'; - blockControls.style.display = 'none'; -} +function updateDockedControlsPosition( newClassName ) { + var isImage = selectedBlock.tagName === 'IMG'; + var className = selectedBlock.className; + var position = selectedBlock.getBoundingClientRect(); + var alignedRight = className.match( /align-right/ ); + var alignedLeft = className.match( /align-left/ ); + var fullBleed = className.match( /full-bleed/ ); + + var topPosition = position.top - 34 + window.scrollY; + var leftPosition = null; + + if ( isImage && alignedRight ) { + leftPosition = position.left; + topPosition = newClassName ? topPosition - 15 : topPosition; + } else if ( isImage && alignedLeft && newClassName ) { + topPosition = topPosition - 15; + } else if ( isImage && className === 'is-selected' && dockedControls.style.left ) { + leftPosition = null; + topPosition = topPosition + 15; + } else if ( fullBleed ) { + leftPosition = ( window.innerWidth / 2 ) - ( dockedControls.clientWidth / 2 ); + } -function hideInlineControls() { - inlineControls.style.display = 'none'; + dockedControls.style.maxHeight = 'none'; + dockedControls.style.top = topPosition + 'px'; + dockedControls.style.left = leftPosition ? leftPosition + 'px' : null; } -// Show popup on text selection -function onSelectText( event ) { - event.stopPropagation(); - var txt = ""; - - if ( window.getSelection ) { - txt = window.getSelection(); - } else if ( document.getSelection ) { - txt = document.getSelection(); - } else if ( document.selection ) { - txt = document.selection.createRange().text; - } - - // Show formatting bar - if ( txt != '' ) { - inlineControls.style.display = 'block'; - var range = txt.getRangeAt(0); - var pos = range.getBoundingClientRect(); - var selectCenter = pos.width / 2; - var controlsCenter = inlineControls.offsetWidth / 2; - inlineControls.style.left = ( pos.left + selectCenter - controlsCenter ) + 'px'; - inlineControls.style.top = ( pos.top - 48 + window.scrollY ) + 'px'; - } else { - inlineControls.style.display = 'none'; - } +function hideControls() { + switcher.style.opacity = 0; + switcherMenu.style.display = 'none'; + dockedControls.style.display = 'none'; } function attachControlActions() { @@ -241,9 +398,11 @@ function attachControlActions() { if ( getter ) { node.addEventListener( 'click', function( event ) { event.stopPropagation(); + var previousOffset = selectedBlock.offsetTop; swapNodes( selectedBlock, getter( selectedBlock ) ); attachBlockHandlers(); reselect(); + window.scrollTo( window.scrollX, window.scrollY + selectedBlock.offsetTop - previousOffset ); }, false ); } } ); @@ -276,12 +435,9 @@ function attachTypeSwitcherActions() { } ); Object.keys( typeToTag ).forEach( function( type ) { - var selector = '.switch-block__block .type-icon-' + type; - var button = queryFirst( selector ); - var label = queryFirst( selector + ' + label' ); - + var iconSelector = '.switch-block__block .type-icon-' + type; + var button = queryFirst( iconSelector ).parentNode; button.addEventListener( 'click', switchBlockType, false ); - label.addEventListener( 'click', switchBlockType, false ); function switchBlockType( event ) { if ( ! selectedBlock ) { @@ -300,7 +456,7 @@ function attachTypeSwitcherActions() { } ); } -function fillBlockMenu() { +function renderBlockMenu() { insertBlockMenuContent.innerHTML = ''; config.blockCategories.forEach( function ( category ) { var node = document.createElement( 'div' ); @@ -314,13 +470,13 @@ function fillBlockMenu() { node.appendChild( nodeBlocks ); var categoryBlocks = config.blocks .filter( function( block ) { - return block.categories.indexOf( category.id ) !== -1 + return block.category === category.id && block.label.toLowerCase().indexOf( searchBlockFilter.toLowerCase() ) !== -1; } ); categoryBlocks .forEach( function( block ) { var node = document.createElement( 'div' ); - node.className = 'insert-block__block'; + node.className = 'insert-block__block block-' + block.id + ( menuSelectedBlock === block ? ' is-active' : '' ); node.innerHTML = block.icon + ' ' + block.label; nodeBlocks.appendChild(node); } ); @@ -329,21 +485,128 @@ function fillBlockMenu() { insertBlockMenuContent.appendChild( node ); } } ); - - var placeholder = document.createElement('div'); - placeholder.className = 'insert-block__separator'; - placeholder.textContent = 'These don\'t work yet.'; - insertBlockMenuContent.appendChild( placeholder ); } function attachBlockMenuSearch() { insertBlockMenuSearchInput.addEventListener( 'keyup', filterBlockMenu, false ); insertBlockMenuSearchInput.addEventListener( 'input', filterBlockMenu, false ); - fillBlockMenu(); + insertBlockMenuContent.addEventListener( 'scroll', handleBlockMenuScroll, false ); + selectBlockInMenu(); + renderBlockMenu(); function filterBlockMenu( event ) { searchBlockFilter = event.target.value; - fillBlockMenu(); + selectBlockInMenu(); + renderBlockMenu(); + } + + function handleBlockMenuScroll( event ) { + if ( insertBlockMenuContent.scrollHeight - insertBlockMenuContent.scrollTop <= insertBlockMenuContent.clientHeight ) { + insertBlockMenuContent.className = 'insert-block__content is-bottom'; + } else { + insertBlockMenuContent.className = 'insert-block__content'; + } + } +} + +/** + * Select a block in the block menu + * @param direction direction from the current position (up/down/left/right) + */ +function selectBlockInMenu( direction ) { + var filteredBlocks = orderedBlocks.filter( function( block ) { + return block.label.toLowerCase().indexOf( searchBlockFilter.toLowerCase() ) !== -1; + } ); + var countBlocksByCategories = filteredBlocks.reduce( function( memo, block ) { + if ( ! memo[ block.category ] ) { + memo[ block.category ] = 0; + } + memo[ block.category ]++; + return memo; + }, {} ); + + var selectedBlockIndex = filteredBlocks.indexOf( menuSelectedBlock ); + selectedBlockIndex = selectedBlockIndex === -1 ? 0 : selectedBlockIndex; + var currentBlock = filteredBlocks[ selectedBlockIndex ]; + var previousBlock = filteredBlocks[ selectedBlockIndex - 1 ]; + var nextBlock = filteredBlocks[ selectedBlockIndex + 1 ]; + var offset = 0; + switch ( direction ) { + case KEY_ARROW_UP: + offset = ( + currentBlock + && filteredBlocks[ selectedBlockIndex - 2 ] + && ( + filteredBlocks[ selectedBlockIndex - 2 ].category === currentBlock.category + || countBlocksByCategories[ previousBlock.category ] % 2 === 0 + ) + ) ? -2 : -1; + break; + case KEY_ARROW_DOWN: + offset = ( + currentBlock + && filteredBlocks[ selectedBlockIndex + 2 ] + && ( + currentBlock.category === filteredBlocks[ selectedBlockIndex + 2 ].category + || filteredBlocks[ selectedBlockIndex + 2 ].category === nextBlock.category + || nextBlock.category === currentBlock.category + ) + ) ? 2 : 1; + break; + case KEY_ARROW_RIGHT: + offset = 1; + break; + case KEY_ARROW_LEFT: + offset = -1; + break; + } + + menuSelectedBlock = filteredBlocks[ selectedBlockIndex + offset ] || menuSelectedBlock; + + // Hack to wait for the rerender before scrolling + setTimeout( function() { + var blockElement = queryFirst( '.insert-block__block.block-' + menuSelectedBlock.id ); + if ( + blockElement && ( + blockElement.offsetTop + blockElement.offsetHeight > insertBlockMenuContent.clientHeight + insertBlockMenuContent.scrollTop + || blockElement.offsetTop < insertBlockMenuContent.scrollTop + ) + ) { + insertBlockMenuContent.scrollTop = blockElement.offsetTop - 23; + } + } ); +} + +function attachKeyboardShortcuts() { + document.addEventListener( 'keypress', handleKeyPress, false ); + document.addEventListener( 'keydown', handleKeyDown, false ); + + function handleKeyPress( event ) { + if ( '/' === String.fromCharCode( event.keyCode ) && ! blockMenuOpened ) { + var focusedBlock = getFocusedBlock(); + if ( document.activeElement !== editor || ( focusedBlock && ! focusedBlock.textContent ) ) { + event.preventDefault(); + openBlockMenu(); + } + } + } + + function handleKeyDown( event ) { + if ( ! blockMenuOpened ) return; + switch ( event.keyCode ) { + case KEY_ENTER: + event.preventDefault(); + hideMenu(); + break; + case KEY_ARROW_DOWN: + case KEY_ARROW_UP: + case KEY_ARROW_LEFT: + case KEY_ARROW_RIGHT: + event.preventDefault(); + selectBlockInMenu( event.keyCode ); + renderBlockMenu(); + break; + } } } @@ -392,15 +655,26 @@ function siblingGetter( direction ) { } function openBlockMenu( event ) { - hideInlineControls(); clearBlocks(); - event.stopPropagation(); + event && event.stopPropagation(); insertBlockMenu.style.display = 'block'; + blockMenuOpened = true; + searchBlockFilter = ''; + insertBlockMenuSearchInput.value = ''; + menuSelectedBlock = false; + previouslyFocusedBlock = getFocusedBlock(); insertBlockMenuSearchInput.focus(); + selectBlockInMenu(); + renderBlockMenu(); } function hideMenu() { + if ( ! blockMenuOpened ) return; insertBlockMenu.style.display = 'none'; + blockMenuOpened = false; + if ( previouslyFocusedBlock ) { + setCaret( previouslyFocusedBlock ); + } } function showSwitcherMenu( event ) { @@ -436,6 +710,16 @@ function setElementState( className, event ) { if ( className ) { selectedBlock.classList.add( className ); } + updateDockedControlsPosition( className ); +} + +function setCaret( element ) { + var range = document.createRange(); + range.setStart( element.childNodes[0] ,0 ); + range.collapse( true ); + var selection = window.getSelection(); + selection.removeAllRanges(); + selection.addRange( range ); } function l( data ) { diff --git a/index.html b/index.html index 59c07a4891173b..9c01eefb3dbe12 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@