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

Parse html into known blocks #100

Conversation

etoledom
Copy link
Contributor

Part of #99

This PR adds the ability of parsing the html string in the html view back into blocks.

For simplicity, the current implementation will just parse the known blocks, and anything that is outside of those blocks will be ignored.

An example on iOS:

parsing_ios

On Android, this currently won't work, but this issue was taken care of by #95. I tested a fast merge and it works:

parse_html_android


There are many details I wasn't sure about, I chose the simplest solutions for all of them, but I hope this PR is a good place to discuss them. The most important ones are:

  • The only way to modify blocks is with a redux action.

The current reducer seems to me that is meant for actions over a single block, but the parse action will regenerate the whole list of blocks.

I was tempted to create another reducer specifically for parser, but it immediately made this implementation (and PR) grow on complexity, so I opted to add an extra action to the current reducer.

  • HTML InputView content as part of the component state.

An interesting question I had was where to keep the state of the text that the user is editing.
My first test was to store it as part of the component state (as the example shows), but for each time the state changes, a new instance of the view is created. This makes the whole UX very bad when editing on a scrolled position.

I also though about the redux store, but I wasn't sure about it and it was more complex than the next option.

I opted to simply store the edited text in a variable.

@etoledom etoledom added the [Type] Enhancement Improves a current area of the editor label Aug 10, 2018
@etoledom etoledom self-assigned this Aug 10, 2018
@etoledom etoledom requested review from hypest and mzorz August 10, 2018 00:07
Mainly to fix `flow` errors. Flow also doesn't like curried annotation :[
@etoledom etoledom mentioned this pull request Aug 10, 2018
2 tasks
@@ -108,6 +108,10 @@ export const reducer = (
blocks.splice( index, 1 );
return { blocks: blocks, refresh: ! state.refresh };
}
case ActionTypes.BLOCK.PARSE: {
const parsed = action.parse(action.payload)
return { blocks: parsed, refresh: state.refresh };
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not import parse in this file instead of passing it down every time you want to use it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The idea was to avoid a hard coded dependency.
But for simplicity I'd agree that it would be better to import it directly. 👍

@@ -108,6 +108,10 @@ export const reducer = (
blocks.splice( index, 1 );
return { blocks: blocks, refresh: ! state.refresh };
}
case ActionTypes.BLOCK.PARSE: {
const parsed = action.parse(action.payload)
return { blocks: parsed, refresh: state.refresh };
Copy link
Contributor

Choose a reason for hiding this comment

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

The whole idea of using Redux store is to take advantage of immutability. when you update reference to the blocks, you can treat it as an indicator if something has changed. This makes usage of refresh boolean flag obsolete as it tracks the state which can be derived from blocks.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So, we should remove refresh from the store, right?
In that case I'd prefer to do that on a separate PR.

@hypest or @mzorz might know why it was necessary to add it?


export const parseBlocksAction: BlockActionType = (payload, parser) => ( {
type: ActionTypes.BLOCK.PARSE,
uid: '',
Copy link
Contributor

Choose a reason for hiding this comment

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

uid can be removed, it is never used. When parsing blocks from HTML you can get multiple blocks and as far as I see, it replaces all content.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If I remove uid, flow gets angry saying that 'uid' is missing in object literal, since BlockActionType expects a uid property.
That's one reason why I thought that a new reducer operating in the same store might be a good idea.

Instead, I'm inclined to define a ParseActionType to keep it simple, remove uid and keep flow happy.

style={ styles.htmlView }>
{ this.serializeToHtml() }
style={ styles.htmlView }
onChangeText={ ( html ) => this.props.html = html }>
Copy link
Contributor

Choose a reason for hiding this comment

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

You should never mutate props, you should track changes to html in state. In this case, you rather should pass down callback to this component which will update html outside.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My original idea was to keep track of the html updates in a simple property this.html, but I kept getting a flow warning about it not being part of the BlockManager type.

I tried to add it as part of the component state but on every update, the InputView is reloaded and it feels weird from a UX point of view (but it works).

What would be the advantage of lifting the html state up vs keeping it in the component's state?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I also tried what is described here (lifting state up), but the jumpiness remains.

@@ -44,6 +51,10 @@ export default class BlockManager extends React.Component<PropsType, StateType>
};
}

componentDidMount() {
this.serializeToHtml();
Copy link
Contributor

Choose a reason for hiding this comment

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

This function returns string, so it should be assigned to a variable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was an experiment and I forgot to remove it 😞
Thanks for catching it!

@gziolo
Copy link
Contributor

gziolo commented Aug 10, 2018

An interesting question I had was where to keep the state of the text that the user is editing.
My first test was to store it as part of the component state (as the example shows), but for each time the state changes, a new instance of the view is created. This makes the whole UX very bad when editing on a scrolled position.

This is because you mutate props.html - see my inline comment :)

The current reducer seems to me that is meant for actions over a single block, but the parse action will regenerate the whole list of blocks.

I had a similar concern. I wouldn't spend too much time doing it yourself using your own store. All those issues are already solved in web version of Gutenberg. Speaking myself, I would focus of integrating functionalities and we can improve the general architecture by integrating with the existing editor store and resetBlocks action:
https://github.com/WordPress/gutenberg/blob/master/packages/editor/src/store/actions.js#L105-L110

Copy link
Contributor

@mzorz mzorz left a comment

Choose a reason for hiding this comment

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

Nice work @etoledom :) I only left a couple of minor comments 👍

@@ -4,6 +4,9 @@

import { reducer } from './';
import * as actions from '../actions/';
import { registerCoreBlocks } from '@gutenberg/core-blocks';

registerCoreBlocks()
Copy link
Contributor

Choose a reason for hiding this comment

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

Small nit - should we register the blocks here or within the specific test, when there's only one test using them? deferring to @hypest here as a related comment is taking place in #88 (comment)

Copy link
Contributor Author

@etoledom etoledom Aug 11, 2018

Choose a reason for hiding this comment

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

I was following the example from block-parser-code.test.js and the others, but if it makes more sense to put them in the test case itself, no problem!

What it feels weird to me is that we are testing parsing two times, one from the parse function directly and other from the redux action indirectly (just the more block for now). Maybe we can find a way to do it just once.

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good! I'll go for it. Thank you


export const parseBlocksAction: ParseActionType = payload => ( {
type: ActionTypes.BLOCK.PARSE,
payload: payload,
Copy link
Contributor

Choose a reason for hiding this comment

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

Small nit - can we rename payload to html as an indication of what the payload is supposed to be about?

@mzorz
Copy link
Contributor

mzorz commented Aug 11, 2018

Tests are failing but they're passing for me locally when running yarn test 🤔

@hypest
Copy link
Contributor

hypest commented Aug 11, 2018

Tests are failing but they're passing for me locally when running yarn test 🤔

FWIW, the tests fail for me locally with the same errors seen on Travis. cc @mzorz

@etoledom
Copy link
Contributor Author

I got the same tests error locally when I updated the branch from master, it was solved when I run git submodule update.

@etoledom
Copy link
Contributor Author

@mzorz I pushed a couple more commits. I hope those handle your comments :)

About the tests, I think I merged wrongly the reference to the gutenberg project and that's why the tests keep failing. I'm looking into it

@mzorz
Copy link
Contributor

mzorz commented Aug 14, 2018

Very nice @etoledom ! 🙇 been testing it quite a bit today

On Android, this currently won't work, but this issue was taken care of by #95. I tested a fast merge and it works:

Given #95 has been closed without merging, due to a better solution to our specific case having been found in #108, I think we still need to address this here. Knowing this might be a bit too much to ask for to do in this PR alone, I went ahead and tried something on my own in #116 (I just didn't feel suggesting something without making sure it would work first was good enough, and so I ended up with a PR 😄).

Hope that's alright! Please feel free to comment / indicate otherwise. Ccing @hypest

HTML parser - restart datasource after full parse of html
Copy link
Contributor

@mzorz mzorz left a comment

Choose a reason for hiding this comment

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

With #116 merged, all is good here! :)

@etoledom etoledom merged commit 2265ab4 into feature/editable-html-view-feature-branch Aug 15, 2018
hypest added a commit that referenced this pull request Jan 3, 2019
Use Bintray mirror of RN when building in Jitpack
@SergioEstevao SergioEstevao deleted the feature/parse-html-into-blocks branch February 7, 2019 13:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Type] Enhancement Improves a current area of the editor
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants