Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add: Code editor to edit site #37765

Merged
merged 2 commits into from
Jan 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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