ProseMirror / prosemirror

The ProseMirror WYSIWYM editor
http://prosemirror.net/
MIT License
7.59k stars 335 forks source link

apply style to selected range without affecting next paragraph #1412

Closed fEyebrow closed 1 year ago

fEyebrow commented 1 year ago

When I selected the entire paragraph and applied the ordered list, the next paragraph was also affected.:

image image

How to avoid this? Sometimes the editor's behavior is right, the next paragraph was not affected.

marijnh commented 1 year ago

I suppose you're selecting from the start of the paragraph to the start of the next paragraph? Because if you actually select only the text in the first paragraph than block-level commands will only affect that paragraph.

fEyebrow commented 1 year ago

how do I fix this, this point can feel confusing to the user. When I select from the start of the paragraph to the start of the next paragraph, the next paragraph will not be affected.

I'm thinking of determining if the selection contains the start of the next paragraph before applying the ordered list, and if so, subtracting “to” by 1. However, I don't know how to determine whether the selected "to" only appears at the beginning of the next paragraph.

fEyebrow commented 1 year ago

this is my solution:

const selection = state.tr.selection
        if (selection.$to.textOffset === 0 && selection.$to.nodeAfter && dispatch) {
          dispatch(state.tr.setSelection(TextSelection.create(
            tr.doc,
            selection.from,
            selection.to - 1)))
        }

I apply the above code before the ordered list. the result is right. Is there any problem I haven't considered? @marijnh

marijnh commented 1 year ago

Different WYSIWYG editors seem to behave differently in this case. Google Docs and CKEditor consider the block that has the end of the selection right at its start to not be part of the selection for block-command purposes. LibreOffice and Lexical do include it. Both seem reasonable, but since ProseMirror has been using the second approach so far, we can't change existing commands having someone's code break.

The proper approach to do this differently would be to define custom commands for things you want to work this way (such as list and block-type related commands) and have them act on a different selection. Your code looks like it is constantly normalizing the selection, which could get annoying in some circumstances. (Also, you'll definitely want an exception for cursor selections at the start of a block.)

fEyebrow commented 1 year ago

I just showed the core code, which is a bit confusing. Here is the complete code: I created a custom command based on the Tiptap API.

import { Extension } from '@tiptap/core'
import { TextSelection } from 'prosemirror-state'

declare module '@tiptap/core' {
  interface Commands<ReturnType> {
    correct: {
      correctBoundary: () => ReturnType
    }
  }
}

const CustomExtension = Extension.create({
  name: 'corret',
  addCommands() {
    return {
      correctBoundary: () => ({ state, dispatch, tr }) => {
        const selection = state.tr.selection
        if (selection.$to.textOffset === 0 && selection.$to.nodeAfter && dispatch) {
          dispatch(state.tr.setSelection(TextSelection.create(
            tr.doc,
            selection.from,
            selection.to - 1)))
        }
        return false
      },
    }
  },
})

export default CustomExtension

Here are the use cases:

editor?.chain().correctBoundary().focus().toggleOrderedList().run()