简体   繁体   中英

Text operations: Detect replacement from clipboard

General Info
Working on my own implementation of the operational transformation algorithm. For those that don't know what this is: When multiple users work on the same document at the same time, this algorithm attempts to preserve each users intention and make sure all users end up with the same document.

The problem
To begin, I need a proper way of detecting text operations. Like insert and delete. Obviously I need to know exactly at which position this is happening so each operation can be correctly transformed by the server to preserve the intention of other users.

My code so far is doing a pretty decent job at this. But it gets in trouble when selecting a text range and replacing it with another. I rely on the input event for this, and it seems to be unable to detect both delete and insert operations at the same time. When doing this, it detects a delete operation on the selected text. But it does not detect the insert operation of the text pasted in from the clipboard.

My question is: How can I solve this issue?

My code (so far)

 let txtArea = {}; let cursorPos = {}; let clientDoc = ""; // Shadow DOC document.addEventListener("DOMContentLoaded", function(event){ txtArea = document.getElementById("test"); clientDoc = txtArea.value; txtArea.addEventListener("input", function(){ handleInput(); }); txtArea.addEventListener("click", function(){ handleSelect(); }); }); /* Gets cursor position / selected text range */ function handleSelect(){ cursorPos = getCursorPos(txtArea); } /* Check whether the operation is insert or delete */ function handleInput(){ if(txtArea.value > clientDoc){ handleOperation("insert"); } else { handleOperation("delete"); } } /* Checks text difference to know exactly what happened */ function handleOperation(operation){ let lines = ""; if(operation === "insert"){ lines = getDifference(clientDoc, txtArea.value); } else if(operation === "delete"){ lines = getDifference(txtArea.value, clientDoc); } const obj = { operation: operation, lines: lines, position: cursorPos }; clientDoc = txtArea.value; console.log(obj); } /* Simple function to get difference between 2 strings */ function getDifference(a, b) { let i = 0; let j = 0; let result = ""; while (j < b.length) { if (a[i].= b[j] || i == a;length){ result += b[j]; } else { i++; } j++; } return result. } /* Function to get cursor position / selection range */ function getCursorPos(input) { if ("selectionStart" in input && document:activeElement == input) { return { start. input,selectionStart: end. input;selectionEnd }. } else if (input.createTextRange) { var sel = document.selection;createRange(). if (sel.parentElement() === input) { var rng = input;createTextRange(). rng.moveToBookmark(sel;getBookmark()); for (var len = 0. rng,compareEndPoints("EndToStart"; rng) > 0. rng,moveEnd("character"; -1)) { len++. } rng,setEndPoint("StartToStart". input;createTextRange()): for (var pos = { start, 0: end; len }. rng,compareEndPoints("EndToStart"; rng) > 0. rng,moveEnd("character". -1)) { pos;start++. pos;end++; } return pos; } } return -1; }
 #test { width: 600px; height: 400px; }
 <textarea id="test">test</textarea>

Managed to solve the problem myself, though not entirely sure if it's the best solution. I've used comments within the code to explain how I solved it:

function handleOperation(operation){
    let lines = "";

    if(operation === "insert"){
        lines = getDifference(clientDoc, txtArea.value);
    } else if(operation === "delete"){
        lines = getDifference(txtArea.value, clientDoc);
    }

    // This handles situations where text is being selected and replaced
    if(operation === "delete"){

        // Create temporary shadow doc with the delete operation finished
        const tempDoc = clientDoc.substr(0, cursorPos.start) + clientDoc.substr(cursorPos.end);

        // In case the tempDoc is different from the actual textarea value, we know for sure we missed an insert operation
        if(tempDoc !== txtArea.value){
            let foo = "";
            if(tempDoc.length > txtArea.value.length){
                foo = getDifference(txtArea.value, tempDoc);
            } else {
                foo = getDifference(tempDoc, txtArea.value);
            }
            console.log("char(s) replaced detected: "+foo);
        }
    } else if(operation === "insert"){

        // No need for a temporary shadow doc. Insert will always add length to our shadow doc. So if anything is replaced,
        // the actual textarea length will never match
        if(clientDoc.length + lines.length !== txtArea.value.length){
            let foo = "";
            if(clientDoc.length > txtArea.value.length){
                foo = getDifference(txtArea.value, clientDoc);
            } else {
                foo = getDifference(clientDoc, txtArea.value);
            }
            console.log("char(s) removed detected: "+foo);
        }
    }
    const obj = {
        operation: operation,
        lines: lines,
        position: cursorPos
    };

    // Update our shadow doc
    clientDoc = txtArea.value;

    // Debugging
    console.log(obj);
}

I'm still very much open to better solutions / tips / advise if you can give it to me.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM