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

Hidden nodes (blocks and inlines) #1211

Closed
alanchrt opened this issue Oct 9, 2017 · 7 comments
Closed

Hidden nodes (blocks and inlines) #1211

alanchrt opened this issue Oct 9, 2017 · 7 comments
Labels

Comments

@alanchrt
Copy link
Contributor

alanchrt commented Oct 9, 2017

This is a feature idea for hidden blocks/inlines as a first-class citizen.

I'm working on folding behavior in my editor. So, in some instances, certain chunks of the document should not be visible.

However, I'm struggling to find a way to implement this. Since Slate operates on the visible content, if the blocks are styled to not be displayed and the user operates on the surrounding area, the blocks can be easily deleted from the document in error. Or, if I style them to be rendered but not visible (absolutely positioned off-screen, for example), they still steal the selection when moving between lines.

If I create a fragment of the blocks and store it elsewhere when folded, then insert it back in when unfolded, I mess with the operation history, which is not ideal for collaborative editing.

So, my suggestion (which I would be willing to look into implementation details and maybe even take a stab at it) is to support an isHidden property on nodes that allows a node to be included in the document, but not rendered. I'm not sure if this would mess with selection, but I'd imagine it could function somewhat like isVoid?

@alanchrt
Copy link
Contributor Author

alanchrt commented Oct 9, 2017

Another angle on it is that it's not Slate's concern to ignore rendering on bits of the document, since that's just presentation. Which, it may be desirable in some cases to have hidden element properties just be presentational and not part of the document data, eg. if the folded state wasn't actually a property of the document, but instead the individual user or device viewing the document (probably will be true in my case). The presentation would be determined by data external to the editor and document.

In that case, the ideal outcome would be that Slate could handle blocks or inlines with display: none and still treat them as part of the document (ie. not delete or replace them on splits and other adjacent operations), but ignore them for selection.

@YurkaninRyan
Copy link
Collaborator

What would happen if you just set the content editable of all of those hidden blocks to false? and then with css made it so they couldn't be selected?

@ianstormtaylor
Copy link
Owner

Hey @alanctkc, interesting problem and good write up!

For all of the solutions you present the problems do seem like they make it impossible to get right.

I think there might be one other user land solution which would be to use onChange (and maybe onBeforeChange to look at the operations that are there and if any of them effect the hidden nodes, to ignore them.

There might be a few pieces missing from core to make this solution super easy to do—allowing onChange to return a new change object entirely being one of them I think. But I think there's enough there to mock-up a hacky solution. And then we could add to core where it makes sense for cleaning it up.

Can you investigate that? Let me know what you find out!

I'd rather not have to add more logic that complicated how core handles changes themselves, since it gets convoluted fast.

@alanchrt
Copy link
Contributor Author

@ianstormtaylor Thanks for the reply! Actually, that's the path I started down last night, and it got relatively complicated pretty quickly. That said, I'm not against complicated if I can close all the loops and it ends up being the most userland-ish.

Here are some things I ran into:

  • The hidden blocks don't necessarily get referenced in the operations. So, I ended up being able to look up the nodes in the operations by path, if they were the right type of blocks, check their siblings (the hidden folded blocks are siblings, not children), and make a little bit of progress. But, what if the hidden block is in the range of a selection? Do I search down every tree in the range for folded blocks?
  • I don't actually want to ignore the operations, but I'd rather unfold the hidden block before the change if it affects the block. So, for example, if you hit delete from the beginning of a visible block following a hidden block, I want to unfold the hidden block and delete the following block text back into that folded block as would happen unfolded. Also, I want some operations to proceed as normal. For example, if you select a range or select all (and hidden blocks are in the selection range), then delete, I want to delete the hidden blocks as well.
  • Because of the above, I need a whitelist of operations to let proceed as normal, (maybe) a list to ignore, and a list to unfold before operating.
  • This is just unfamiliarity with Slate, but how would I alter the change in onBeforeChange to insert operations before the ones already in the change? Is the operations property mutable, or is there some other API?

@YurkaninRyan that's actually a super interesting idea messing with contenteditable, and one I hadn't thought about at all. Theoretically, I could style stuff off-screen and avoid it stealing the selection during movement. Copy and paste might be a little unexpected, but I'm not actually against having the folded blocks show up in pastes. Does Slate provide an API for contenteditable manipulation, or am I on my own trying to gain control of the DOM over React's binding? Sounds pretty hacky in the latter case.

@ianstormtaylor
Copy link
Owner

@alanctkc can you give a visual example of a delete operation involving a fold? Not sure I totally understand what your desired outcome it.

@alanchrt
Copy link
Contributor Author

Wellllllll, shoot. I did some testing to try to get a visual, and actually discovered Slate is doing all the right things under the hood for folding to work properly, with no accidentally destructive operations. Whenever performing operations around hidden blocks, they are actually treated as if they were still in the document like visible blocks. The data is right there, and I somehow completely missed it.

My problem was in the way I referenced blocks for unfolding... the fold/unfold UI was on a sibling block of the hidden block, so if the sibling relationship changed during an operation, then I wasn't able to unfold the hidden block by referencing it as a sibling of the visible UI, and I mistook that for the hidden block being deleted (it's not).

So, all that to say, there are a few tweaks I need to make to preserve references to hidden blocks properly, and a couple edge case behaviors I'd like to modify, but Slate is totally doing the expected thing and isn't a part of the equation at all. In fact, I'm not building a code editor (Ace already got that one in the bag), but the folding behavior I have using Slate seems to already behave almost identical to folding in my code editor out of the box.

Thanks for the attention to this and sorry for the noise! Let me know if it's still not clear, but it seems like Slate already supports hidden nodes. :)

@ianstormtaylor
Copy link
Owner

Haha awesome 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants