feat: add parentScale #18

Closed
xuejian97 wants to merge 1 commit from feat/parent-scale into master
xuejian97 commented 2023-08-24 17:06:22 +02:00 (Migrated from github.com)

Description

When the editor or its parent container has the transform.scale style and the value is not equal to 1, the drop cursor position will be misaligned when the text is selected and dragged in the editor.

Changelog

  1. Add an optional property parentScale to address the cursor misaligned problem caused by setting a non-1 scale on the parent component. The default value is 1.
  2. Modify the code in the updateOverlay method about calculating the width, height, and positioning of the cursor element, the new code will correct the calculation according to the parentScale value, and keep the same logic with the original code when the parentScale is 1 by default.

Details

  1. parentScale is an optional parameter, it should be set only when the scale style is set by the editor or parent component and the value is not equal to 1.
  2. The type of the parentScale parameter can be number or (()=>number).
    • When the scale of the parent component is fixed, you can pass in the same value as the scale of the parent component.
    • When the parent component scale value is variable, you can pass in a function that returns a number type and implement the logic to return the parent component scale value in the function.

Screenshots

I set the dropcursor property to {width: 10, color: "orange"} to make the cursor stand out more in the screenshot.

Before:
cursor_misaligned_problem
After:
fixed

Code Samples

Here's a code sample that can be used to reproduce the cursor misalignment problem and to validate the modified code. Replacing the following code with the contents of the corresponding directory file in the demo should work fine.

demo/index.html

<!doctype html>

<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ProseMirror demo page</title>
<link rel=stylesheet href="demo.css">
<link rel=stylesheet href="parent/view/style/prosemirror.css">
<link rel=stylesheet href="parent/menu/style/menu.css">
<link rel=stylesheet href="parent/example-setup/style/style.css">
<link rel=stylesheet href="parent/gapcursor/style/gapcursor.css">

<h1>ProseMirror demo page</h1>

<div style="display: flex;">
    Current scale:&nbsp;
    <div class="app-btn" onclick="handleClickMinus()">-</div>
    <div id="scale-box"></div>
    <div class="app-btn" onclick="handleClickPlus()">+</div>
</div>
<div class="full" style="transform-origin: left top"></div>

<div id=content style="display: none">
    <h2>Demonstration Text</h2>

    <p>A ProseMirror document is based on a schema, which determines the
        kind of elements that may occur in it, and the relation they have to
        each other. This one is based on the basic schema, with lists and
        tables added. It allows the usual <strong>strong</strong>
        and <em>emphasized</em> text, <code>code font</code>,
        and <a href="http://marijnhaverbeke.nl">links</a>. There are also
        images: <img alt="demo picture" src="img.png">.</p>

    <p>On the block level you can have:</p>

    <ol>
        <li>Ordered lists (such as this one)</li>
        <li>Bullet lists</li>
        <li>Blockquotes</li>
        <li>Code blocks</li>
        <li>Tables</li>
        <li>Horizontal rules</li>
    </ol>

    <p>It isn't hard to define your own custom elements, and include them
        in your schema. These can be opaque 'leaf' nodes, that the user
        manipulates through extra interfaces you provide, or nodes with
        regular editable child nodes.</p>

    <hr>

    <h2>The Model</h2>

    <p>Nodes can nest arbitrarily deep. Thus, the document forms a tree,
        not dissimilar to the browser's DOM tree.</p>

    <p>At the inline level, the model works differently. Each block of
        text is a single node containing a flat series of inline elements.
        These are serialized as a tree structure when outputting HTML.</p>

    <p>Positions in the document are represented as a path (an array of
        offsets) through the block tree, and then an offset into the inline
        content of the block. Blocks that have no inline content (such as
        horizontal rules and HTML blocks) can not have the cursor inside of
        them. User-exposed operations on the document preserve the invariant
        that there is always at least a single valid cursor position.</p>

    <hr>

    <h2>Examples</h2>

    <blockquote>
        <blockquote><p>We did not see a nested blockquote
            yet.</p></blockquote>
    </blockquote>

    <pre><code class="lang-markdown">Nor did we see a code block

Note that the content of a code block can't be styled.</code></pre>

    <p>This paragraph has<br>a hard break inside of it.</p>
</div>

<script type=module src="_m/demo.js"></script>
<script>
    let scale = 1.0;

    function handleClickMinus() {
        if (scale === 0.3) {
            return;
        }
        scale -= 0.1;
        scale = Number.parseFloat(scale.toFixed(1));
        changeScaleText()
    }

    function handleClickPlus() {
        if (scale === 5) {
            return;
        }
        scale += 0.1;
        scale = Number.parseFloat(scale.toFixed(1));
        changeScaleText()
    }

    function changeScaleText() {
        let element = document.getElementById("scale-box");
        element.innerText = scale;
        changeContentScale()
    }

    function changeContentScale() {
        let elements = document.getElementsByClassName("full");
        if (elements.length !== 1) {
            return;
        }
        localStorage.setItem("scale", scale);

        let element = elements[0];
        element.style.transform = `scale(${scale})`;
    }

    changeScaleText();
</script>

<style>
    .app-btn {
        border: 1px solid black;
        width: 20px;
        height: 20px;
        text-align: center;
        line-height: 20px;
        user-select: none;
    }

    .app-btn:hover {
        cursor: pointer;
    }

    #scale-box {
        margin: 0 10px;
        width: 20px;
        text-align: center;
    }
</style>

example-setup/src/index.ts

This can be reproduced or verified by toggling the code in the dropCursor comments section of the plugins.

import {keymap} from "prosemirror-keymap"
import {history} from "prosemirror-history"
import {baseKeymap} from "prosemirror-commands"
import {Plugin} from "prosemirror-state"
import {dropCursor} from "prosemirror-dropcursor"
import {gapCursor} from "prosemirror-gapcursor"
import {menuBar, MenuItem} from "prosemirror-menu"
import {Schema} from "prosemirror-model"

import {buildMenuItems} from "./menu"
import {buildKeymap} from "./keymap"
import {buildInputRules} from "./inputrules"

export {buildMenuItems, buildKeymap, buildInputRules}

/// Create an array of plugins pre-configured for the given schema.
/// The resulting array will include the following plugins:
///
///  * Input rules for smart quotes and creating the block types in the
///    schema using markdown conventions (say `"> "` to create a
///    blockquote)
/// 
///  * A keymap that defines keys to create and manipulate the nodes in the
///    schema
/// 
///  * A keymap binding the default keys provided by the
///    prosemirror-commands module
/// 
///  * The undo history plugin
/// 
///  * The drop cursor plugin
/// 
///  * The gap cursor plugin
/// 
///  * A custom plugin that adds a `menuContent` prop for the
///    prosemirror-menu wrapper, and a CSS class that enables the
///    additional styling defined in `style/style.css` in this package
///
/// Probably only useful for quickly setting up a passable
/// editor—you'll need more control over your settings in most
/// real-world situations.
export function exampleSetup(options: {
    /// The schema to generate key bindings and menu items for.
    schema: Schema

    /// Can be used to [adjust](#example-setup.buildKeymap) the key bindings created.
    mapKeys?: { [key: string]: string | false }

    /// Set to false to disable the menu bar.
    menuBar?: boolean

    /// Set to false to disable the history plugin.
    history?: boolean

    /// Set to false to make the menu bar non-floating.
    floatingMenu?: boolean

    /// Can be used to override the menu content.
    menuContent?: MenuItem[][]
}) {
    let plugins = [
        buildInputRules(options.schema),
        keymap(buildKeymap(options.schema, options.mapKeys)),
        keymap(baseKeymap),

        // Using this line of code and setting the scale to a value not equal to 1 can reproduce the misaligned cursor problem
        dropCursor({width: 10, color: "orange"}),

        // Modified code. The dropcursor/src/dropcursor.ts code needs to be replaced with the code below.
        // dropCursor({
        //     width: 10, color: "orange", parentScale: () => {
        //         let scale_str = localStorage.getItem("scale");
        //         let scale = 1;
        //         if (scale_str !== null) {
        //             scale = Number.parseFloat(scale_str);
        //         }
        //         return scale;
        //     }
        // }),

        gapCursor()
    ]
    if (options.menuBar !== false)
        plugins.push(menuBar({
            floating: options.floatingMenu !== false,
            content: options.menuContent || buildMenuItems(options.schema).fullMenu
        }))
    if (options.history !== false)
        plugins.push(history())

    return plugins.concat(new Plugin({
        props: {
            attributes: {class: "ProseMirror-example-setup-style"}
        }
    }))
}

dropcursor/src/dropcursor.ts

import {Plugin, EditorState} from "prosemirror-state"
import {EditorView} from "prosemirror-view"
import {dropPoint} from "prosemirror-transform"

interface DropCursorOptions {
  /// The color of the cursor. Defaults to `black`. Use `false` to apply no color and rely only on class.
  color?: string | false

  /// The precise width of the cursor in pixels. Defaults to 1.
  width?: number

  /// A CSS class name to add to the cursor element.
  class?: string

  /// The parentScale addresses misaligned cursor during dragging caused by non-1 transform.scale of the editor's parent component. Defaults to 1.
  parentScale?: number | (() => number)
}

/// Create a plugin that, when added to a ProseMirror instance,
/// causes a decoration to show up at the drop position when something
/// is dragged over the editor.
///
/// Nodes may add a `disableDropCursor` property to their spec to
/// control the showing of a drop cursor inside them. This may be a
/// boolean or a function, which will be called with a view and a
/// position, and should return a boolean.
export function dropCursor(options: DropCursorOptions = {}): Plugin {
  return new Plugin({
    view(editorView) { return new DropCursorView(editorView, options) }
  })
}

class DropCursorView {
  width: number
  color: string | undefined
  class: string | undefined
  parentScale: number | (() => number)
  cursorPos: number | null = null
  element: HTMLElement | null = null
  timeout: number = -1
  handlers: {name: string, handler: (event: Event) => void}[]

  constructor(readonly editorView: EditorView, options: DropCursorOptions) {
    this.width = options.width ?? 1
    this.color = options.color === false ? undefined : (options.color || "black")
    this.class = options.class
    this.parentScale = options.parentScale ?? 1;

    this.handlers = ["dragover", "dragend", "drop", "dragleave"].map(name => {
      let handler = (e: Event) => { (this as any)[name](e) }
      editorView.dom.addEventListener(name, handler)
      return {name, handler}
    })
  }

  destroy() {
    this.handlers.forEach(({name, handler}) => this.editorView.dom.removeEventListener(name, handler))
  }

  update(editorView: EditorView, prevState: EditorState) {
    if (this.cursorPos != null && prevState.doc != editorView.state.doc) {
      if (this.cursorPos > editorView.state.doc.content.size) this.setCursor(null)
      else this.updateOverlay()
    }
  }

  setCursor(pos: number | null) {
    if (pos == this.cursorPos) return
    this.cursorPos = pos
    if (pos == null) {
      this.element!.parentNode!.removeChild(this.element!)
      this.element = null
    } else {
      this.updateOverlay()
    }
  }

  updateOverlay() {
    let $pos = this.editorView.state.doc.resolve(this.cursorPos!)
    let isBlock = !$pos.parent.inlineContent, rect
    if (isBlock) {
      let before = $pos.nodeBefore, after = $pos.nodeAfter
      if (before || after) {
        let node = this.editorView.nodeDOM(this.cursorPos! - (before ?  before.nodeSize : 0))
        if (node) {
          let nodeRect = (node as HTMLElement).getBoundingClientRect()
          let top = before ? nodeRect.bottom : nodeRect.top
          if (before && after)
            top = (top + (this.editorView.nodeDOM(this.cursorPos!) as HTMLElement).getBoundingClientRect().top) / 2
          rect = {left: nodeRect.left, right: nodeRect.right, top: top - this.width / 2, bottom: top + this.width / 2}
        }
      }
    }
    if (!rect) {
      let coords = this.editorView.coordsAtPos(this.cursorPos!)
      rect = {left: coords.left - this.width / 2, right: coords.left + this.width / 2, top: coords.top, bottom: coords.bottom}
    }

    let parent = this.editorView.dom.offsetParent!
    if (!this.element) {
      this.element = parent.appendChild(document.createElement("div"))
      if (this.class) this.element.className = this.class
      this.element.style.cssText = "position: absolute; z-index: 50; pointer-events: none;"
      if (this.color) {
        this.element.style.backgroundColor = this.color
      }
    }
    this.element.classList.toggle("prosemirror-dropcursor-block", isBlock)
    this.element.classList.toggle("prosemirror-dropcursor-inline", !isBlock)
    let parentLeft, parentTop
    if (!parent || parent == document.body && getComputedStyle(parent).position == "static") {
      parentLeft = -pageXOffset
      parentTop = -pageYOffset
    } else {
      let rect = parent.getBoundingClientRect()
      parentLeft = rect.left - parent.scrollLeft
      parentTop = rect.top - parent.scrollTop
    }

    const scale: number = typeof this.parentScale === "number" ? this.parentScale : this.parentScale();
    const elementWidth = (rect.right - rect.left) / (isBlock ? scale : 1);
    const elementHeight = (rect.bottom - rect.top) / (!isBlock ? scale : 1);
    this.element.style.width = elementWidth + "px"
    this.element.style.height = elementHeight + "px"
    this.element.style.left = (rect.left - (!isBlock ? (elementWidth / 2) * (scale - 1) : 0) - parentLeft) / scale + "px"
    this.element.style.top = (rect.top - (isBlock ? (elementHeight / 2) * (scale - 1) : 0) - parentTop) / scale + "px"
  }

  scheduleRemoval(timeout: number) {
    clearTimeout(this.timeout)
    this.timeout = setTimeout(() => this.setCursor(null), timeout)
  }

  dragover(event: DragEvent) {
    if (!this.editorView.editable) return
    let pos = this.editorView.posAtCoords({left: event.clientX, top: event.clientY})

    let node = pos && pos.inside >= 0 && this.editorView.state.doc.nodeAt(pos.inside)
    let disableDropCursor = node && node.type.spec.disableDropCursor
    let disabled = typeof disableDropCursor == "function" ? disableDropCursor(this.editorView, pos, event) : disableDropCursor

    if (pos && !disabled) {
      let target: number | null = pos.pos
      if (this.editorView.dragging && this.editorView.dragging.slice) {
        let point = dropPoint(this.editorView.state.doc, target, this.editorView.dragging.slice)
        if (point != null) target = point
      }
      this.setCursor(target)
      this.scheduleRemoval(5000)
    }
  }

  dragend() {
    this.scheduleRemoval(20)
  }

  drop() {
    this.scheduleRemoval(20)
  }

  dragleave(event: DragEvent) {
    if (event.target == this.editorView.dom || !this.editorView.dom.contains((event as any).relatedTarget))
      this.setCursor(null)
  }
}

Thank you for reviewing my PR!

### Description When the editor or its parent container has the `transform.scale` style and the value is not equal to 1, the drop cursor position will be misaligned when the text is selected and dragged in the editor. ### Changelog 1. Add an optional property `parentScale` to address the cursor misaligned problem caused by setting a non-1 scale on the parent component. The default value is 1. 2. Modify the code in the `updateOverlay` method about calculating the width, height, and positioning of the cursor element, the new code will correct the calculation according to the `parentScale` value, and keep the same logic with the original code when the `parentScale` is 1 by default. ### Details 1. `parentScale` is an optional parameter, it should be set only when the scale style is set by the editor or parent component and the value is not equal to 1. 2. The type of the `parentScale` parameter can be `number` or `(()=>number)`. - When the scale of the parent component is fixed, you can pass in the same value as the scale of the parent component. - When the parent component scale value is variable, you can pass in a function that returns a number type and implement the logic to return the parent component scale value in the function. ### Screenshots I set the dropcursor property to {width: 10, color: "orange"} to make the cursor stand out more in the screenshot. Before: <img width="545" alt="cursor_misaligned_problem" src="https://github.com/ProseMirror/prosemirror-dropcursor/assets/110003514/29a14c29-f730-4df3-b6ae-db49cb53f829"> After: <img width="465" alt="fixed" src="https://github.com/ProseMirror/prosemirror-dropcursor/assets/110003514/e3184daa-4d16-4e53-a93d-784b4873feaf"> ### Code Samples Here's a code sample that can be used to reproduce the cursor misalignment problem and to validate the modified code. Replacing the following code with the contents of the corresponding directory file in the demo should work fine. #### demo/index.html ```html <!doctype html> <meta charset="utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>ProseMirror demo page</title> <link rel=stylesheet href="demo.css"> <link rel=stylesheet href="parent/view/style/prosemirror.css"> <link rel=stylesheet href="parent/menu/style/menu.css"> <link rel=stylesheet href="parent/example-setup/style/style.css"> <link rel=stylesheet href="parent/gapcursor/style/gapcursor.css"> <h1>ProseMirror demo page</h1> <div style="display: flex;"> Current scale:&nbsp; <div class="app-btn" onclick="handleClickMinus()">-</div> <div id="scale-box"></div> <div class="app-btn" onclick="handleClickPlus()">+</div> </div> <div class="full" style="transform-origin: left top"></div> <div id=content style="display: none"> <h2>Demonstration Text</h2> <p>A ProseMirror document is based on a schema, which determines the kind of elements that may occur in it, and the relation they have to each other. This one is based on the basic schema, with lists and tables added. It allows the usual <strong>strong</strong> and <em>emphasized</em> text, <code>code font</code>, and <a href="http://marijnhaverbeke.nl">links</a>. There are also images: <img alt="demo picture" src="img.png">.</p> <p>On the block level you can have:</p> <ol> <li>Ordered lists (such as this one)</li> <li>Bullet lists</li> <li>Blockquotes</li> <li>Code blocks</li> <li>Tables</li> <li>Horizontal rules</li> </ol> <p>It isn't hard to define your own custom elements, and include them in your schema. These can be opaque 'leaf' nodes, that the user manipulates through extra interfaces you provide, or nodes with regular editable child nodes.</p> <hr> <h2>The Model</h2> <p>Nodes can nest arbitrarily deep. Thus, the document forms a tree, not dissimilar to the browser's DOM tree.</p> <p>At the inline level, the model works differently. Each block of text is a single node containing a flat series of inline elements. These are serialized as a tree structure when outputting HTML.</p> <p>Positions in the document are represented as a path (an array of offsets) through the block tree, and then an offset into the inline content of the block. Blocks that have no inline content (such as horizontal rules and HTML blocks) can not have the cursor inside of them. User-exposed operations on the document preserve the invariant that there is always at least a single valid cursor position.</p> <hr> <h2>Examples</h2> <blockquote> <blockquote><p>We did not see a nested blockquote yet.</p></blockquote> </blockquote> <pre><code class="lang-markdown">Nor did we see a code block Note that the content of a code block can't be styled.</code></pre> <p>This paragraph has<br>a hard break inside of it.</p> </div> <script type=module src="_m/demo.js"></script> <script> let scale = 1.0; function handleClickMinus() { if (scale === 0.3) { return; } scale -= 0.1; scale = Number.parseFloat(scale.toFixed(1)); changeScaleText() } function handleClickPlus() { if (scale === 5) { return; } scale += 0.1; scale = Number.parseFloat(scale.toFixed(1)); changeScaleText() } function changeScaleText() { let element = document.getElementById("scale-box"); element.innerText = scale; changeContentScale() } function changeContentScale() { let elements = document.getElementsByClassName("full"); if (elements.length !== 1) { return; } localStorage.setItem("scale", scale); let element = elements[0]; element.style.transform = `scale(${scale})`; } changeScaleText(); </script> <style> .app-btn { border: 1px solid black; width: 20px; height: 20px; text-align: center; line-height: 20px; user-select: none; } .app-btn:hover { cursor: pointer; } #scale-box { margin: 0 10px; width: 20px; text-align: center; } </style> ``` #### example-setup/src/index.ts This can be reproduced or verified by toggling the code in the dropCursor comments section of the plugins. ```html import {keymap} from "prosemirror-keymap" import {history} from "prosemirror-history" import {baseKeymap} from "prosemirror-commands" import {Plugin} from "prosemirror-state" import {dropCursor} from "prosemirror-dropcursor" import {gapCursor} from "prosemirror-gapcursor" import {menuBar, MenuItem} from "prosemirror-menu" import {Schema} from "prosemirror-model" import {buildMenuItems} from "./menu" import {buildKeymap} from "./keymap" import {buildInputRules} from "./inputrules" export {buildMenuItems, buildKeymap, buildInputRules} /// Create an array of plugins pre-configured for the given schema. /// The resulting array will include the following plugins: /// /// * Input rules for smart quotes and creating the block types in the /// schema using markdown conventions (say `"> "` to create a /// blockquote) /// /// * A keymap that defines keys to create and manipulate the nodes in the /// schema /// /// * A keymap binding the default keys provided by the /// prosemirror-commands module /// /// * The undo history plugin /// /// * The drop cursor plugin /// /// * The gap cursor plugin /// /// * A custom plugin that adds a `menuContent` prop for the /// prosemirror-menu wrapper, and a CSS class that enables the /// additional styling defined in `style/style.css` in this package /// /// Probably only useful for quickly setting up a passable /// editor—you'll need more control over your settings in most /// real-world situations. export function exampleSetup(options: { /// The schema to generate key bindings and menu items for. schema: Schema /// Can be used to [adjust](#example-setup.buildKeymap) the key bindings created. mapKeys?: { [key: string]: string | false } /// Set to false to disable the menu bar. menuBar?: boolean /// Set to false to disable the history plugin. history?: boolean /// Set to false to make the menu bar non-floating. floatingMenu?: boolean /// Can be used to override the menu content. menuContent?: MenuItem[][] }) { let plugins = [ buildInputRules(options.schema), keymap(buildKeymap(options.schema, options.mapKeys)), keymap(baseKeymap), // Using this line of code and setting the scale to a value not equal to 1 can reproduce the misaligned cursor problem dropCursor({width: 10, color: "orange"}), // Modified code. The dropcursor/src/dropcursor.ts code needs to be replaced with the code below. // dropCursor({ // width: 10, color: "orange", parentScale: () => { // let scale_str = localStorage.getItem("scale"); // let scale = 1; // if (scale_str !== null) { // scale = Number.parseFloat(scale_str); // } // return scale; // } // }), gapCursor() ] if (options.menuBar !== false) plugins.push(menuBar({ floating: options.floatingMenu !== false, content: options.menuContent || buildMenuItems(options.schema).fullMenu })) if (options.history !== false) plugins.push(history()) return plugins.concat(new Plugin({ props: { attributes: {class: "ProseMirror-example-setup-style"} } })) } ``` #### dropcursor/src/dropcursor.ts ```html import {Plugin, EditorState} from "prosemirror-state" import {EditorView} from "prosemirror-view" import {dropPoint} from "prosemirror-transform" interface DropCursorOptions { /// The color of the cursor. Defaults to `black`. Use `false` to apply no color and rely only on class. color?: string | false /// The precise width of the cursor in pixels. Defaults to 1. width?: number /// A CSS class name to add to the cursor element. class?: string /// The parentScale addresses misaligned cursor during dragging caused by non-1 transform.scale of the editor's parent component. Defaults to 1. parentScale?: number | (() => number) } /// Create a plugin that, when added to a ProseMirror instance, /// causes a decoration to show up at the drop position when something /// is dragged over the editor. /// /// Nodes may add a `disableDropCursor` property to their spec to /// control the showing of a drop cursor inside them. This may be a /// boolean or a function, which will be called with a view and a /// position, and should return a boolean. export function dropCursor(options: DropCursorOptions = {}): Plugin { return new Plugin({ view(editorView) { return new DropCursorView(editorView, options) } }) } class DropCursorView { width: number color: string | undefined class: string | undefined parentScale: number | (() => number) cursorPos: number | null = null element: HTMLElement | null = null timeout: number = -1 handlers: {name: string, handler: (event: Event) => void}[] constructor(readonly editorView: EditorView, options: DropCursorOptions) { this.width = options.width ?? 1 this.color = options.color === false ? undefined : (options.color || "black") this.class = options.class this.parentScale = options.parentScale ?? 1; this.handlers = ["dragover", "dragend", "drop", "dragleave"].map(name => { let handler = (e: Event) => { (this as any)[name](e) } editorView.dom.addEventListener(name, handler) return {name, handler} }) } destroy() { this.handlers.forEach(({name, handler}) => this.editorView.dom.removeEventListener(name, handler)) } update(editorView: EditorView, prevState: EditorState) { if (this.cursorPos != null && prevState.doc != editorView.state.doc) { if (this.cursorPos > editorView.state.doc.content.size) this.setCursor(null) else this.updateOverlay() } } setCursor(pos: number | null) { if (pos == this.cursorPos) return this.cursorPos = pos if (pos == null) { this.element!.parentNode!.removeChild(this.element!) this.element = null } else { this.updateOverlay() } } updateOverlay() { let $pos = this.editorView.state.doc.resolve(this.cursorPos!) let isBlock = !$pos.parent.inlineContent, rect if (isBlock) { let before = $pos.nodeBefore, after = $pos.nodeAfter if (before || after) { let node = this.editorView.nodeDOM(this.cursorPos! - (before ? before.nodeSize : 0)) if (node) { let nodeRect = (node as HTMLElement).getBoundingClientRect() let top = before ? nodeRect.bottom : nodeRect.top if (before && after) top = (top + (this.editorView.nodeDOM(this.cursorPos!) as HTMLElement).getBoundingClientRect().top) / 2 rect = {left: nodeRect.left, right: nodeRect.right, top: top - this.width / 2, bottom: top + this.width / 2} } } } if (!rect) { let coords = this.editorView.coordsAtPos(this.cursorPos!) rect = {left: coords.left - this.width / 2, right: coords.left + this.width / 2, top: coords.top, bottom: coords.bottom} } let parent = this.editorView.dom.offsetParent! if (!this.element) { this.element = parent.appendChild(document.createElement("div")) if (this.class) this.element.className = this.class this.element.style.cssText = "position: absolute; z-index: 50; pointer-events: none;" if (this.color) { this.element.style.backgroundColor = this.color } } this.element.classList.toggle("prosemirror-dropcursor-block", isBlock) this.element.classList.toggle("prosemirror-dropcursor-inline", !isBlock) let parentLeft, parentTop if (!parent || parent == document.body && getComputedStyle(parent).position == "static") { parentLeft = -pageXOffset parentTop = -pageYOffset } else { let rect = parent.getBoundingClientRect() parentLeft = rect.left - parent.scrollLeft parentTop = rect.top - parent.scrollTop } const scale: number = typeof this.parentScale === "number" ? this.parentScale : this.parentScale(); const elementWidth = (rect.right - rect.left) / (isBlock ? scale : 1); const elementHeight = (rect.bottom - rect.top) / (!isBlock ? scale : 1); this.element.style.width = elementWidth + "px" this.element.style.height = elementHeight + "px" this.element.style.left = (rect.left - (!isBlock ? (elementWidth / 2) * (scale - 1) : 0) - parentLeft) / scale + "px" this.element.style.top = (rect.top - (isBlock ? (elementHeight / 2) * (scale - 1) : 0) - parentTop) / scale + "px" } scheduleRemoval(timeout: number) { clearTimeout(this.timeout) this.timeout = setTimeout(() => this.setCursor(null), timeout) } dragover(event: DragEvent) { if (!this.editorView.editable) return let pos = this.editorView.posAtCoords({left: event.clientX, top: event.clientY}) let node = pos && pos.inside >= 0 && this.editorView.state.doc.nodeAt(pos.inside) let disableDropCursor = node && node.type.spec.disableDropCursor let disabled = typeof disableDropCursor == "function" ? disableDropCursor(this.editorView, pos, event) : disableDropCursor if (pos && !disabled) { let target: number | null = pos.pos if (this.editorView.dragging && this.editorView.dragging.slice) { let point = dropPoint(this.editorView.state.doc, target, this.editorView.dragging.slice) if (point != null) target = point } this.setCursor(target) this.scheduleRemoval(5000) } } dragend() { this.scheduleRemoval(20) } drop() { this.scheduleRemoval(20) } dragleave(event: DragEvent) { if (event.target == this.editorView.dom || !this.editorView.dom.contains((event as any).relatedTarget)) this.setCursor(null) } } ``` Thank you for reviewing my PR!
marijnh commented 2023-09-11 13:14:00 +02:00 (Migrated from github.com)

Does patch af2c980, which attempts to make this work without action from the user, work for you?

Does patch af2c980, which attempts to make this work without action from the user, work for you?
xuejian97 commented 2023-09-17 15:54:05 +02:00 (Migrated from github.com)

Does patch af2c980, which attempts to make this work without action from the user, work for you?

Yes, but the width or height of drop cursor doesn't look correct when scale is not 1. I tried to fix this issue in #19. please check, thanks.

> Does patch [af2c980](https://github.com/ProseMirror/prosemirror-dropcursor/commit/af2c980cc2e531a33de2354eeb1730c5dbf2461a), which attempts to make this work without action from the user, work for you? Yes, but the width or height of drop cursor doesn't look correct when scale is not 1. I tried to fix this issue in [#19](https://github.com/ProseMirror/prosemirror-dropcursor/pull/19). please check, thanks.

Pull request closed

Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
prosemirror/prosemirror-dropcursor!18
No description provided.