tinymce / tinymce

The world's #1 JavaScript library for rich text editing. Available for React, Vue and Angular
https://www.tiny.cloud
Other
14.93k stars 2.23k forks source link

Disable indent when it reaches the right edge #9679

Open LiamHanninenEvosus opened 4 months ago

LiamHanninenEvosus commented 4 months ago

📝 Provide a description of the new feature or improvement

Can the right/normal indent button get disabled when it gets to the right-most spot of the text field (similar to the left/decrease indent does now at the left-most spot). Otherwise as you can test on this inline editor example or see in the attached video - the words or paragraph run right over the edge of the text box.

https://github.com/tinymce/tinymce/assets/166527952/e879070c-0aa7-49c0-9d68-62c1cd463eeb

🫶 What is the motivation?

We have clients that WILL indent until it falls out of the textbox. They then think they lost the data. We need to protect them from themselves.

🔗 What is the consequence of not having this feature?

This will result in perceived loss of data and more work for our front office.

🚦 How important would you rate the requested feature or improvement?

Critical for us since we use this in ~200 places in our application. But I could see how it would be less critical for others.

Additional Notes

I was able to implement this in a basic way (see attached 20 sec video). The click limit is dynamic based on the width of the text box. But it fails when I have multiple <p> tags and indent different <p> tags (<p> tags surface when users create new lines by pressing 'Enter' on their keyboard). The click count doesn't track which <p> was indented nor which one is getting indented so the whole strategy breaks down.

https://github.com/tinymce/tinymce/assets/166527952/ad3b2cfb-de26-43e1-96f8-d1a76f2fa00c


If you'd like to see this implemented sooner, add a 👍 reaction to this post.

TheSpyder commented 4 months ago

The problem is that nothing prevents margin-left from being able to push content off the side of the screen. The best I think we could offer is a "maximum margin left" configuration option, but that seems like a weird concept to explain.

I'm happy to see you already worked out how to intercept the indent command, so my suggestion is rather than base it on number of clicks you instead inspect the margin-left on the block the cursor is in. Base the enabled state on that instead of number of clicks.

If you can get that working, then instead of waiting for the indent command you can add a NodeChange event listener to the editor which will happen when the cursor moves between elements (which is the only time UI needs to update).

LiamHanninenEvosus commented 4 months ago

I think I understand - I will try that out. Thanks!

LiamHanninenEvosus commented 4 months ago

Yea - checking for padding-left will be much simpler. Thanks.

I can't figure how to leverage a NodeChange event listener. I found it on this page in the docs but my simplest tests don't show any console logs. I've tried variations of this:

tinymce.init({
  selector: 'textarea',
  init_instance_callback: (editor) => {
    editor.on('onNodeChange', (e) => {
      console.log(`The ${e.command} command was fired.`);
    });
  }
});

Pressing indent, changing cursor placement or typing does not trigger it. I tried implementing it on my own in dev tools but perhaps I'm not targeting the right element.

TheSpyder commented 4 months ago

Oh of course it's padding-left, not margin-left, my bad.

Your code is incorrect. Remove the second on; the event name is simply NodeChange. on is the function name that adds a listener for the event.

LiamHanninenEvosus commented 4 months ago

Thanks. In the meantime we were able to get it to work using ExecCommand. And this seems to work if we check for command is 'indent'. And checking the padding/margin like you suggested solves the complexity of the problem because we don't have to keep track of anything (clicks). We just set a max padding for that particular <p> or we can dynamically check the size of the text box to determine max padding.

One strange last thing - is our attempts to appropriately disable the indent isn't working. We tried all of these

e.stopPropagation();

e.preventDefault();

e.stopImmediatePropagation();

and the indent button is still clickable. All this to say: Is the disabling you use for the left/un-indent accessible to us? If not we'll just do it 'manually'. Thanks for your help. Either way we're in a better place with this and it's 'resolved' for us. image

                tinymce.init({
                selector: 'textarea',
                setup: function (editor) {
                    editor.on('ExecCommand', function (e) {
                        console.log('Command pressed');
                        // debugger
                        if (e.command === 'indent') {
                          console.log('Indent pressed');
                          debugger;
                          var focusedElement = editor.selection.getNode();
                          var computedStyle = window.getComputedStyle(focusedElement);
                          var currentMarginLeft = parseFloat(computedStyle.paddingLeft);
                          var maxPaddingLeft = 100;

                          if (currentMarginLeft >= maxPaddingLeft) {
                            // Prevent the default indent action
                            e.stopPropagation();
                            e.preventDefault();
                            e.stopImmediatePropagation();
                            console.error('Indent prevented: Margin limit reached');
                          }
                        }})                    
                }
TheSpyder commented 4 months ago

our attempts to appropriately disable the indent isn't working

If you read the docs closely, you want BeforeExecCommand. It might be clearer if you look at the editor code for executing commands.

No, we don't have a way to disable the indent button which does make my suggestion for using NodeChange less relevant than I thought it was. It would be reasonable for us to support registering a queryCommandState handler for this, because we already use one for outdent: https://github.com/tinymce/tinymce/blob/f6a87ed7a6d32c627cf8ac6c1bd44ce36d69b14b/modules/tinymce/src/themes/silver/main/ts/ui/core/IndentOutdent.ts#L6-L9

Indent just wasn't included because we never disable it ourselves. If the code was extended to include indent state querying, you could use addQueryStateHandler to respond every time the editor checks if indent should be enabled (which would be done on node change, as it is with outent).

If you're comfortable in the code this might be suitable for a community PR.

LiamHanninenEvosus commented 4 months ago

Unfortunately TS/JS is not a 'native' language for me. So I doubt my code could fit nicely into this repo.

You were right actually - NodeChangeis the way to go. BeforeExecCommand gets me 90% of the way. But when I change lines I need the indent button to toggle editability if necessary. BeforeExecCommand doesn't fire on line change as I'm sure you know. As you can see in the video it does generally work AND it enables/disables correctly when there are multiple lines. But there is confusing UX if it doesn't react on cursor line change.

NodeChangedoesn't have a command property, which makes sense. So is there a clean way to get the indent button from editor class or a NodeChangeEvent? Like editor.toolbar.getButton('indent') :) ? This would be more than just convenient because we use both inline and iframe - so toolbar button accessibility is a little complex. Otherwise I'll just grab it via normal getElementbyID + queryselector.

https://github.com/tinymce/tinymce/assets/166527952/59cbc9a7-6d2a-4762-9897-5224e612fa67

TheSpyder commented 3 months ago

Ah, yes that was why I suggested nodechange initially.

Unfortunately we don't have a central store to retrieve buttons from. Both buttons and menu items are treated as ephemeral and recreated as needed.

The only reliable fix is to have the internal code use the state query API which you can then hook into.