Skip to content

Commit

Permalink
Add: Code editor to edit site (#37765)
Browse files Browse the repository at this point in the history
  • Loading branch information
jorgefilipecosta authored Jan 24, 2022
1 parent afa6767 commit 8d7dc99
Show file tree
Hide file tree
Showing 16 changed files with 399 additions and 10 deletions.
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/base-styles/_z-index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ $z-layers: (
".block-editor-block-list__block-selection-button": 22,
".components-form-toggle__input": 1,
".edit-post-text-editor__toolbar": 1,
".edit-site-code-editor__toolbar": 1,
".edit-post-sidebar__panel-tab.is-active": 1,

// These next three share a stacking context
Expand Down
1 change: 1 addition & 0 deletions packages/edit-site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"history": "^5.1.0",
"jszip": "^3.2.2",
"lodash": "^4.17.21",
"react-autosize-textarea": "^7.1.0",
"rememo": "^3.0.0"
},
"publishConfig": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* External dependencies
*/
import Textarea from 'react-autosize-textarea';

/**
* WordPress dependencies
*/
/**
* WordPress dependencies
*/
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { useState } from '@wordpress/element';
import { useInstanceId } from '@wordpress/compose';
import { VisuallyHidden } from '@wordpress/components';

export default function CodeEditorTextArea( { value, onChange, onInput } ) {
const [ stateValue, setStateValue ] = useState( value );
const [ isDirty, setIsDirty ] = useState( false );
const instanceId = useInstanceId( CodeEditorTextArea );

if ( ! isDirty && stateValue !== value ) {
setStateValue( value );
}

/**
* Handles a textarea change event to notify the onChange prop callback and
* reflect the new value in the component's own state. This marks the start
* of the user's edits, if not already changed, preventing future props
* changes to value from replacing the rendered value. This is expected to
* be followed by a reset to dirty state via `stopEditing`.
*
* @see stopEditing
*
* @param {Event} event Change event.
*/
const onChangeHandler = ( event ) => {
const newValue = event.target.value;
onInput( newValue );
setStateValue( newValue );
setIsDirty( true );
};

/**
* Function called when the user has completed their edits, responsible for
* ensuring that changes, if made, are surfaced to the onPersist prop
* callback and resetting dirty state.
*/
const stopEditing = () => {
if ( isDirty ) {
onChange( stateValue );
setIsDirty( false );
}
};

return (
<>
<VisuallyHidden
as="label"
htmlFor={ `code-editor-text-area-${ instanceId }` }
>
{ __( 'Type text or HTML' ) }
</VisuallyHidden>
<Textarea
autoComplete="off"
dir="auto"
value={ stateValue }
onChange={ onChangeHandler }
onBlur={ stopEditing }
className="edit-site-code-editor-text-area"
id={ `code-editor-text-area-${ instanceId }` }
placeholder={ __( 'Start writing with text or HTML' ) }
/>
</>
);
}
65 changes: 65 additions & 0 deletions packages/edit-site/src/components/code-editor/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* WordPress dependencies
*/
import { parse } from '@wordpress/blocks';
import { useEntityBlockEditor, useEntityProp } from '@wordpress/core-data';
import { useSelect, useDispatch } from '@wordpress/data';
import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts';
import { __ } from '@wordpress/i18n';
import { Button } from '@wordpress/components';

/**
* Internal dependencies
*/
import { store as editSiteStore } from '../../store';
import CodeEditorTextArea from './code-editor-text-area';

export default function CodeEditor() {
const { templateType, shortcut } = useSelect( ( select ) => {
const { getEditedPostType } = select( editSiteStore );
const { getShortcutRepresentation } = select( keyboardShortcutsStore );
return {
templateType: getEditedPostType(),
shortcut: getShortcutRepresentation( 'core/edit-site/toggle-mode' ),
};
}, [] );
const [ contentStructure, setContent ] = useEntityProp(
'postType',
templateType,
'content'
);
const [ blocks, , onChange ] = useEntityBlockEditor(
'postType',
templateType
);
const content =
contentStructure instanceof Function
? contentStructure( { blocks } )
: contentStructure;
const { switchEditorMode } = useDispatch( editSiteStore );
return (
<div className="edit-site-code-editor">
<div className="edit-site-code-editor__toolbar">
<h2>{ __( 'Editing code' ) }</h2>
<Button
variant="tertiary"
onClick={ () => switchEditorMode( 'visual' ) }
shortcut={ shortcut }
>
{ __( 'Exit code editor' ) }
</Button>
</div>
<div className="edit-site-code-editor__body">
<CodeEditorTextArea
value={ content }
onChange={ ( newContent ) => {
onChange( parse( newContent ), {
selection: undefined,
} );
} }
onInput={ setContent }
/>
</div>
</div>
);
}
100 changes: 100 additions & 0 deletions packages/edit-site/src/components/code-editor/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
.edit-site-code-editor {
position: relative;
width: 100%;
background-color: $white;
flex-grow: 1;

&__body {
width: 100%;
padding: 0 $grid-unit-15 $grid-unit-15 $grid-unit-15;
max-width: $break-xlarge;
margin-left: auto;
margin-right: auto;

@include break-large() {
padding: $grid-unit-20 $grid-unit-30 #{ $grid-unit-60 * 2 } $grid-unit-30;
padding: 0 $grid-unit-30 $grid-unit-30 $grid-unit-30;
}
}

// Exit code editor toolbar.
&__toolbar {
position: sticky;
z-index: z-index(".edit-site-code-editor__toolbar");
top: 0;
left: 0;
right: 0;
display: flex;
background: rgba($white, 0.8);
padding: $grid-unit-05 $grid-unit-15;

@include break-small() {
padding: $grid-unit-15;
}

@include break-large() {
padding: $grid-unit-15 $grid-unit-30;
}

h2 {
line-height: $button-size;
margin: 0 auto 0 0;
font-size: $default-font-size;
color: $gray-900;
}

.components-button svg {
order: 1;
}
}
}

textarea.edit-site-code-editor-text-area.edit-site-code-editor-text-area {
border: $border-width solid $gray-600;
border-radius: 0;
display: block;
margin: 0;
width: 100%;
box-shadow: none;
resize: none;
overflow: hidden;
font-family: $editor-html-font;
line-height: 2.4;
min-height: 200px;
transition: border 0.1s ease-out, box-shadow 0.1s linear;
@include reduce-motion("transition");

// Same padding as title.
padding: $grid-unit-20;
@include break-small() {
padding: $grid-unit-30;
}

/* Fonts smaller than 16px causes mobile safari to zoom. */
font-size: $mobile-text-min-font-size !important;
@include break-small {
font-size: $text-editor-font-size !important;
}

&:focus {
border-color: var(--wp-admin-theme-color);
box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color);

// Elevate the z-index on focus so the focus style is uncropped.
position: relative;
}

&::-webkit-input-placeholder {
color: $dark-gray-placeholder;
}

&::-moz-placeholder {
color: $dark-gray-placeholder;
// Override Firefox default.
opacity: 1;
}

&:-ms-input-placeholder {
color: $dark-gray-placeholder;
}
}
23 changes: 16 additions & 7 deletions packages/edit-site/src/components/editor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import Header from '../header';
import { SidebarComplementaryAreaFills } from '../sidebar';
import NavigationSidebar from '../navigation-sidebar';
import BlockEditor from '../block-editor';
import CodeEditor from '../code-editor';
import KeyboardShortcuts from '../keyboard-shortcuts';
import URLQueryController from '../url-query-controller';
import InserterSidebar from '../secondary-sidebar/inserter-sidebar';
Expand Down Expand Up @@ -60,6 +61,7 @@ function Editor( { onError } ) {
isNavigationOpen,
previousShortcut,
nextShortcut,
editorMode,
} = useSelect( ( select ) => {
const {
isInserterOpened,
Expand All @@ -69,6 +71,7 @@ function Editor( { onError } ) {
getEditedPostId,
getPage,
isNavigationOpened,
getEditorMode,
} = select( editSiteStore );
const { hasFinishedResolution, getEntityRecord } = select( coreStore );
const postType = getEditedPostType();
Expand Down Expand Up @@ -102,6 +105,7 @@ function Editor( { onError } ) {
nextShortcut: select(
keyboardShortcutsStore
).getAllShortcutKeyCombinations( 'core/edit-site/next-region' ),
editorMode: getEditorMode(),
};
}, [] );
const { setPage, setIsInserterOpened } = useDispatch( editSiteStore );
Expand Down Expand Up @@ -220,13 +224,18 @@ function Editor( { onError } ) {
content={
<>
<EditorNotices />
{ template && (
<BlockEditor
setIsInserterOpen={
setIsInserterOpened
}
/>
) }
{ editorMode === 'visual' &&
template && (
<BlockEditor
setIsInserterOpen={
setIsInserterOpened
}
/>
) }
{ editorMode === 'text' &&
template && (
<CodeEditor />
) }
{ templateResolved &&
! template &&
settings?.siteUrl &&
Expand Down
12 changes: 10 additions & 2 deletions packages/edit-site/src/components/header/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { useSelect, useDispatch } from '@wordpress/data';
import { PinnedItems } from '@wordpress/interface';
import { _x, __ } from '@wordpress/i18n';
import { listView, plus } from '@wordpress/icons';
import { Button } from '@wordpress/components';
import { Button, ToolbarItem } from '@wordpress/components';
import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts';
import { store as editorStore } from '@wordpress/editor';
import { store as coreStore } from '@wordpress/core-data';
Expand Down Expand Up @@ -45,13 +45,15 @@ export default function Header( {
isListViewOpen,
listViewShortcut,
isLoaded,
isVisualMode,
} = useSelect( ( select ) => {
const {
__experimentalGetPreviewDeviceType,
getEditedPostType,
getEditedPostId,
isInserterOpened,
isListViewOpened,
getEditorMode,
} = select( editSiteStore );
const { getEditedEntityRecord } = select( coreStore );
const { __experimentalGetTemplateInfo: getTemplateInfo } = select(
Expand All @@ -75,6 +77,7 @@ export default function Header( {
listViewShortcut: getShortcutRepresentation(
'core/edit-site/toggle-list-view'
),
isVisualMode: getEditorMode() === 'visual',
};
}, [] );

Expand Down Expand Up @@ -111,6 +114,7 @@ export default function Header( {
variant="primary"
isPressed={ isInserterOpen }
className="edit-site-header-toolbar__inserter-toggle"
disabled={ ! isVisualMode }
onMouseDown={ preventDefault }
onClick={ openInserter }
icon={ plus }
Expand All @@ -121,11 +125,15 @@ export default function Header( {
/>
{ isLargeViewport && (
<>
<ToolSelector />
<ToolbarItem
as={ ToolSelector }
disabled={ ! isVisualMode }
/>
<UndoButton />
<RedoButton />
<Button
className="edit-site-header-toolbar__list-view-toggle"
disabled={ ! isVisualMode }
icon={ listView }
isPressed={ isListViewOpen }
/* translators: button label text should, if possible, be under 16 characters. */
Expand Down
Loading

0 comments on commit 8d7dc99

Please sign in to comment.