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

Introduce new CodeMirror block for editing code. #665

Closed
wants to merge 2 commits into from

Conversation

dmsnell
Copy link
Member

@dmsnell dmsnell commented May 4, 2017

While demonstrating Gutenberg to my team the question was asked about
editing code blocks: "it's so terribly difficult in the WordPress editor."

Wanting to demonstrate some of the flexibility that the block system
provides I created a new block type which turns a normal block into an
instance of the CodeMirror editor.

Issues:

  • How do we determine which modes to show? I think we should allow for all of them but have a better way to select the common modes. Maybe an auto-completing drop-down menu in the inspector.
  • Travis fails on CodeMirror's CSS but it runs locally

codemirrorblock

Also shown here with a degraded network connection to show the async code loading in when requested
codemirrorlazyloading

@dmsnell dmsnell force-pushed the try/new-block-codemirror branch from 7d0defe to 32275e5 Compare May 4, 2017 17:25
@mtias mtias added the [Status] In Progress Tracking issues with work in progress label May 4, 2017
@BE-Webdesign
Copy link
Contributor

does anyone care about this? any reason to keep it around?

I think this would be an awesome thing to have in the editor. This could also potentially become a good test case for building a plugin that registers a block. I doubt the vast majority of WordPress users will need/want this kind of content block, but any programmer would probably really like this!

@dmsnell
Copy link
Member Author

dmsnell commented May 19, 2017

thanks @BE-Webdesign - your comment makes me realize that I should create this at some point outside of the project as a new-editor plugin!~

closing for now then

@dmsnell dmsnell closed this May 19, 2017
@dmsnell dmsnell deleted the try/new-block-codemirror branch May 19, 2017 20:47
@dmsnell dmsnell restored the try/new-block-codemirror branch June 11, 2017 12:25
@dmsnell dmsnell reopened this Jun 11, 2017
@dmsnell dmsnell force-pushed the try/new-block-codemirror branch 4 times, most recently from 19674f4 to 06893cb Compare June 11, 2017 13:08
@dmsnell dmsnell requested review from aduth, mtias and ellatrix June 11, 2017 13:09
@dmsnell dmsnell force-pushed the try/new-block-codemirror branch from 086e7b4 to d75a2fc Compare June 11, 2017 13:54
@dmsnell dmsnell added [Feature] Rich Text Related to the Rich Text component that allows developers to render a contenteditable and removed [Status] In Progress Tracking issues with work in progress labels Jun 11, 2017
const { html } = query;

const toEditor = s => he.decode( s.split( '<br>' ).join( '\n' ) );
const fromEditor = s => he.encode( s, { useNamedReferences: true } ).split( '\n' ).join( '<br>' );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you use text to query for the attribute, and <pre><code>, this is all not needed? React will do it by itself? That's what I did with the plain code block.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

…will investigate, thanks!

have you not had any problems with special characters or whitespace? can I get rid of he altogether?

(note also that he has a few cool points, like being able to choose how and when characters get encoded)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can remove it entirely, yes. It will convert to named entities.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(note also that he has a few cool points, like being able to choose how and when characters get encoded)

It's also a behemoth of a library, at least 24kb minified + gzipped. Might be a point toward dynamically enqueueing block scripts separately based on presence in content.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks! I pulled it in initially because it was a prototype. I'm very glad you were aware of how <pre><code> changed the semantics. much better now!

edit( { attributes, setAttributes } ) {
return (
<div>
<select onBlur={ ( { target: { value } } ) => setAttributes( { language: value } ) }>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't onChange be better?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

had onChange to begin with but eslint yapped at me.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

used onChange - please see my new comment asking about this…

{ [ 'css', 'diff', 'javascript', 'markdown', 'pegjs', 'php' ].map( language => (
<option
key={ language }
selected={ language === attributes.language }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getting a warning:

Warning: Use the defaultValue or value props on <select> instead of setting selected on <option>.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks. I honestly didn't pay any attention to this (obviously). it seems like I need to make it a setting in the page inspector too but at the time I didn't know about that

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

used value

post-content.js Outdated
@@ -163,6 +163,10 @@ window._wpGutenbergPost = {
'https://make.wordpress.org/core/2017/01/17/editor-technical-overview/',
'<!-- /wp:core/embed -->',

'<!-- wp:core/code-mirror language="php" -->',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JS? :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

…will change to Elm 😉

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to Elm 😄

@dmsnell dmsnell force-pushed the try/new-block-codemirror branch from d75a2fc to a331c56 Compare June 13, 2017 20:44
@dmsnell
Copy link
Member Author

dmsnell commented Jun 13, 2017

I've got some difficulty understanding the CSS errors. They are coming in via CodeMirror.

Honestly I would prefer to be able to dynamically load the CSS but I'm not sure how that would happen. if someone would be willing to help me through these errors/async issues I'll buy you an 🍦

@dmsnell dmsnell added the [Feature] Block API API that allows to express the block paradigm. label Jun 13, 2017
require( 'codemirror/mode/markdown/markdown' );
require( 'codemirror/mode/pegjs/pegjs' );
require( 'codemirror/mode/php/php' );
require( 'codemirror/lib/codemirror.css' );
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are more of these. if we had a way to async load them I would do so on-demand and provide the full suite with a better language selector

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you try using import instead of require here? It should teach Webpack to async load all those files. I guess we would still have to wrap CodeMirror into its own component to make sure those deps are loaded only when it mounts.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried using import() and it didn't seem to recognize it. In d957054 I added the use of require.ensure() which seems to mostly work. There is still an issue that it's bundling the entirety of the modes directory into one chunk instead of many.

@dmsnell dmsnell force-pushed the try/new-block-codemirror branch from f1f4488 to b88aa08 Compare June 15, 2017 11:12
@westonruter
Copy link
Member

FYI: @georgestephanis has a feature plugin for introducing CodeMirror into other areas of WP. The block perhaps could be made part of that plugin and re-use the copy of CodeMirror that it includes.

See https://github.com/georgestephanis/codemirror-wp

@georgestephanis
Copy link
Contributor

Just for a separation of concerns, and to keep Gutenberg a little bit lighter, it might be wise for the Gutenberg version of a code editor block to just be a plain textarea, and have the CodeMirror-WP plugin handle turning it more interactive and engaging?

I'm happy to add anyone to the cm repo, just ask.

@dmsnell
Copy link
Member Author

dmsnell commented Jun 15, 2017

and have the CodeMirror-WP plugin handle turning it more interactive and engaging?

thanks @georgestephanis. the block editor is a very good place, in my opinion, to pull in code mirror. since it is implementation specific I would imagine a WordPress plugin that both registers the editor block and includes the script into the browser.

this PR isn't going to be considered ready regardless until we can asynchronously load in CodeMirror and its modes. it would keep Gutenberg lightweight and make code editing first-class.

actually I would prefer to generalize it as something like "syntax editor" or "code editor" in its name so we could swap out CodeMirror if we wanted. (it started out this way as "code" until @iseulde took that name)

@dmsnell dmsnell force-pushed the try/new-block-codemirror branch from f42a1e3 to 5660dd1 Compare July 4, 2017 14:04
Copy link
Member

@gziolo gziolo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have async loading of the library yet?

Is this the only remaining blocker here? Do we want to have it in core or rather as a plugin? I'm happy to help with this one.

@gziolo
Copy link
Member

gziolo commented Sep 22, 2017

Reopening for growing interest

I'm not surprised :) Using CodeMirror was my first thought when I saw the existing code block 😄 Thanks for opening this PR!

@mtias
Copy link
Member

mtias commented Sep 22, 2017

@gziolo WordPress 4.9 will ship with it, so we can enqueue it from there, which would make things easier. It'd be nice to still async load it, if that's something you want to explore.

@gziolo gziolo assigned gziolo and unassigned gziolo Sep 22, 2017
@ellatrix
Copy link
Member

If core adds it, let's add it to the default code block, but also keep a "plain" type (no language). Also note we'll need highlighting on the front-end. Prism is quite nice.

@mtias
Copy link
Member

mtias commented Sep 22, 2017

We used it for the docs: http://gutenberg-devdoc.surge.sh/block-api/

With a tweaked stylesheet.

@mtias
Copy link
Member

mtias commented Sep 22, 2017

And agreed that if it lands in core we should make it part of the main block.

@gziolo gziolo mentioned this pull request Sep 22, 2017
3 tasks
@dmsnell dmsnell force-pushed the try/new-block-codemirror branch from 5660dd1 to 272c6e6 Compare September 23, 2017 22:39
@dmsnell
Copy link
Member Author

dmsnell commented Sep 24, 2017

This has been updated to lazy-load CodeMirror and also the language modes. There are some problems indicated inside the code itself on those. Notably:

  • When dynamically loading the CSS it ends up rendering wrong so I have left the CSS as a static import
  • The mode files are all being bundled into one file instead of having separate chunks for each mode. This is related to require.context() and webpack statically anticipating the import. I believe that by doing some twiddling we can get what we want. I'm having a hard time really understanding what webpack is doing. After some rework I was able to split the mode files into separate bundles by adding statically-assigned System.import() statements

At first I tried manually inserting a new <script> tag into the document head, which was loading files as requested, but the paths were screwy and I had issues with the loaded files expecting CodeMirror to be a global in a specific state which didn't make sense. Ideally I think we'd rely on webpack to handle the glue for us.

The tests are still failing and I didn't really bother to look at why. The PR has known issues to address before fixing the tests.

For some reason the demo content is showing "This block appears to have been modified externally" but I don't know why. Also, I cannot click on the options in that dialog. I can tab to the buttons "Overwrite" and "Convert to classic text" but the clicks are completely lost. Also, I don't know what "Overwrite" means there. The block was missing the CSS class in post-content.js but that has been added and the warning is gone.

@dmsnell dmsnell force-pushed the try/new-block-codemirror branch from c344cb1 to fab4ec9 Compare September 24, 2017 01:25
// @see node_modules/codemirror/modes
// when supported we can probably replace with standard `import()` syntax
const modes = {
apl: { label: 'APL', req: () => System.import( 'codemirror/mode/apl/apl' ) },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

System.import is deprecated. You should use dynamic import instead`. See webpack 3 announcement post: https://medium.com/webpack/webpack-3-official-release-15fd2dd8f07b:

import(/* webpackChunkName: "my-chunk-name" */ 'module');

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just noticed we are on Webpack 2.7, but I think it still supports import, but without this magic comments.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the comment. import() was throwing errors as not supported

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can double check though too

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we miss something in Babel config ¯_(ツ)_/¯

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gziolo and @aduth I'm getting this when trying to use import()

ERROR in ./blocks/library/code-mirror/index.js
Module build failed: SyntaxError: Unexpected token (24:33)

Not seeing a source of that error either.

Copy link
Member Author

@dmsnell dmsnell Oct 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

babel/babel-loader#493

according to that ^^^ it seems like it's still experimental in babel so even though we have webpack support for it we're not going to get it unless we pull in a new dependency. I'm happy leaving this PR without additional dependencies.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/tc39/proposal-dynamic-import - it's stage 3. It learned yesterday that Gutenberg uses only features that are accepted by ECMA TC39. So this one needs to wait. It's good as it is :)

@dmsnell dmsnell force-pushed the try/new-block-codemirror branch 3 times, most recently from d85bc27 to 6b222b5 Compare October 4, 2017 16:05
// when this CSS was imported dynamically
// it was causing major visual glitches to
// the editor which completely broke it
require( 'codemirror/lib/codemirror.css' );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All styles are loaded using the following syntax:

import './style.scss';

Webpack uses the following pattern test: /style\.s?css$/, to match styles. So it should work here, too. Unless this is the reason why it breaks :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually I think it had to do with race conditions on the mounted components. I was comparing to async loading as the dynamic bits though vs. import statement.

mainly I meant to convey that this stylesheet seemed to need to load before the editor and I wasn't sure how best to do that

@gziolo
Copy link
Member

gziolo commented Oct 6, 2017

The tests are still failing and I didn't really bother to look at why. The PR has known issues to address before fixing the tests.

Yes, it's enforced to add fixtures for all blocks in here: https://github.com/WordPress/gutenberg/blob/master/blocks/test/full-content.js#L198.

While demonstrating Gutenberg to my team the question was asked about
editing code blocks: "it's so terribly difficult in the WordPress
editor."

Wanting to demonstrate some of the flexibility that the block system
provides I created a new block type which turns a normal block into an
instance of the CodeMirror editor.

This should eventually replace the standard `code` block once it's ready
for production. At the moment it lazy-loads the `CodeMirror` editor
_and_ the mode files, so thankfully this keeps the initial builds small.
@dmsnell dmsnell force-pushed the try/new-block-codemirror branch from a714d0d to 94abcdc Compare January 25, 2018 17:12
@karmatosed
Copy link
Member

As talked about in this week's chat, closing this as now Codemirror is in for one block it needs iterating.

@karmatosed karmatosed closed this Mar 7, 2018
@gziolo
Copy link
Member

gziolo commented Mar 7, 2018

https://github.com/WordPress/gutenberg/blob/master/components/code-editor/index.js - we have CodeMirror integrated here, we should try to use a similar approach or even better extend the existing one to make it more flexible.

@gziolo gziolo deleted the try/new-block-codemirror branch March 7, 2018 14:33
@dmsnell
Copy link
Member Author

dmsnell commented Mar 7, 2018

😢

this was my favorite block

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Block API API that allows to express the block paradigm. [Feature] Rich Text Related to the Rich Text component that allows developers to render a contenteditable
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants