0

I'm pretty new to coding, and have been making a Text Editor component using the textarea: https://jsfiddle.net/chrismg12/jLk70ar3/5/

I was going to make a function that's selects all instances of a found word. However I don't know how to do so in a textarea.NOTE:This is not about multiple carets, but rather multiple selection, however advice in implementations of multiple carets would be heavily appreciated.
HTML

var lNo = "";

function injectStyles(rule) {
  var div = $("<div />", {
    html: '&shy;<style>' + rule + '</style>'
  }).appendTo("body");
}

$.fn.selectRange = function(start, end) {
  return this.each(function() {
    if (typeof end == "undefined") {
      end = start;
    }
    if (start == -1) {
      start = this.value.length;
    }
    if (end == -1) {
      end = this.value.length;
    }
    if (this.setSelectionRange) {
      this.focus();
      this.setSelectionRange(start, end + 1);
    } else if (this.createTextRange) {
      var range = this.createTextRange();
      range.collapse(true);
      range.moveEnd('character', end);
      range.moveStart('character', start);
      range.select();
    }
  });
};

$.fn.selectRangeExcludingLast = function(start, end) {
  return this.each(function() {
    if (typeof end == "undefined") {
      end = start;
    }
    if (start == -1) {
      start = this.value.length;
    }
    if (end == -1) {
      end = this.value.length;
    }
    if (this.setSelectionRange) {
      this.focus();
      this.setSelectionRange(start, end + 1);
    } else if (this.createTextRange) {
      var range = this.createTextRange();
      range.collapse(true);
      range.moveEnd('character', end);
      range.moveStart('character', start);
      range.select();
    }
  });
};

function getPos(str, substr, index) {
  var io = str.split(substr, index).join(substr).length;
  if (io >= str.length) {
    return -1;
  } else {
    return io;
  }
}

function gutterChildControl(gutter, textarea, settings) {
  if (gutter.childNodes.length != $(textarea).val().split("\n").length) {
    while (gutter.childNodes.length < $(textarea).val().split("\n").length) {
      var gutterChild = document.createElement("div");
      gutterChild.className = "gutterChild"
      gutterChild.style.height = $(textarea).css('line-height');
      var gutterChildLineHeight = $(textarea).css('line-height');
      $(gutterChild).css('line-height', gutterChildLineHeight)
      gutterChild.style.fontSize = $(textarea).css('font-size');
      gutterChild.style.width = "100%";
      gutterChild.style.color = settings.gFontCol;
      gutterChild.style.paddingRight = `${settings.gPadding}px`;
      gutterChild.style.paddingLeft = `${settings.gPadding}px`;
      gutterChild.style.textAlign = settings.gFontAlign;
      gutterChild.onmouseover = function() {
        gutterChild.style.opacity = "0.75"
      }
      gutterChild.onmouseleave = function() {
        gutterChild.style.opacity = "1"
      }
      gutterChild.onclick = function() {
        if (parseInt(gutterChild.innerHTML) != 1) {
          startPos = getPos($(textarea).val(), "\n", parseInt(gutterChild.innerHTML) - 1)
          console.log(`startPos:${startPos}`);
          endPos = getPos($(textarea).val(), "\n", parseInt(gutterChild.innerHTML))
          if (endPos == -1) {
            endPos = $(textarea).val().length - 1
          }
          console.log(`endPos:${endPos}`)
          $(textarea).selectRange(startPos, endPos);
        } else {
          startPos = 0;
          console.log(`startPos:${startPos}`)
          endPos = getPos($(textarea).val(), "\n", 1)
          if (endPos == -1) {
            endPos = $(textarea).val().length - 1
            if (endPos == -1) {
              endPos = 0;
            }
          }
          console.log(`endPos:${endPos}`)
          $(textarea).selectRange(startPos, endPos);
        }
      }

      gutter.appendChild(gutterChild);
      gutterChild.innerHTML = `${gutter.childNodes.length}`
    }
    while (gutter.childNodes.length > $(textarea).val().split("\n").length) {
      gutter.removeChild(gutter.lastChild)
    }
    // while(gutter.childNodes.length > $(textarea).val().split("\n").length){
    //  gutter.childNodes.pop;
    // }
  }
}

function Ventify(element, options) {
  const settings = {
    taCol: "#1d252c",
    gCol: "#1d252c",
    fSize: "22px",
    gFontCol: "rgba(58,74,88,1)",
    pad: 1,
    gFontAlign: "center",
    gPadding: 20,
    ...options
  }
  element.style.display = "flex";
  element.style.flexDirection = "row";
  var gutter = document.createElement("div");
  var textarea = document.createElement("textarea");
  gutter.className = "gutter";
  gutter.classList.add("scrll")
  textarea.className = "ventiEditor";
  textarea.classList.add("scrll")

  gutter.style.width = "100px";
  gutter.style.height = "100%";
  gutter.style.backgroundColor = settings.gCol;
  gutter.style.overflowY = "scroll"
  gutter.style.scrollbarWidth = "none";
  injectStyles(".gutter::-webkit-scrollbar{width:0px;}")
  injectStyles(".ventiEditor{outline:0px !important;-webkit-appearance:none;}")

  textarea.style.width = "calc(100% - 100px)";
  textarea.style.fontSize = settings.fSize;
  textarea.style.color = settings.gFontCol;
  textarea.style.overflowY = "scroll";
  textarea.style.whiteSpace = "pre";
  textarea.style.resize = "none";
  textarea.style.height = "100%";
  textarea.style.margin = "0px 0px 0px 0px"
  textarea.style.border = "0px solid rgb(255,255,255)"
  textarea.style.padding = "0"
  $(textarea).css('line-height', `calc(${2*settings.pad}px + ${settings.fSize})`)
  textarea.style.backgroundColor = settings.taCol;
  textarea.spellcheck = false;
  $(textarea).css("-moz-tab-size", "4");
  $(textarea).css("tab-size", "4")
  $('.ventiEditor:focus').css('outline', '0px !important')
  $('.ventiEditor:focus').css('-webkit-appearance', 'none')
  textarea.oninput = function() {
    var gutter = textarea.parentElement.childNodes[0];
    gutterChildControl(gutter, textarea, settings)
  };
  textarea.onscroll = function() {
    gutter.scrollTop = textarea.scrollTop
  }
  $(textarea).keydown(function(e1) {
    if (e1.ctrlKey) {
      if (e1.keyCode >= 48 && e1.keyCode <= 57) {
        lNo += e1.key;
        console.log(lNo);
      }
    }
  })
  // gutter.onscroll = function(){
  //  if(gutter.scrollTop != textarea.scrollTop){
  //      textarea.scrollTop = gutter.scrollTop;
  //  }
  // }
  element.appendChild(gutter);
  element.appendChild(textarea);
  gutterChildControl(gutter, textarea, settings);
}

function gutterFix(textarea) {}

$(document).delegate('.ventiEditor', 'keydown', function(e) {
  var keyCode = e.keyCode || e.which;

  if (keyCode == 9) {
    e.preventDefault();
    var start = this.selectionStart;
    var end = this.selectionEnd;

    // set textarea value to: text before caret + tab + text after caret
    $(this).val($(this).val().substring(0, start) +
      "\t" +
      $(this).val().substring(end));

    // put caret at right position again
    this.selectionStart =
      this.selectionEnd = start + 1;
  }
});

injectStyles(".gutterChild{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}")
Ventify(document.getElementsByClassName("container")[0], {
  taCol: "#1d252c",
  gCol: "#1d252c",
  pad: 5,
  gFontCol: '#bbb'
})
html,
body {
  height: 100%;
  margin: 0px 0px 0px 0px;
}

.container {
  height: 100%;
}
<link rel="stylesheet" href="bootstrap-4.3.1-dist/css/bootstrap.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="container"></div>
Rory McCrossan
  • 331,213
  • 40
  • 305
  • 339
Apoqlite
  • 239
  • 2
  • 21

1 Answers1

1

You could set up an empty array and push the positions of all the matching strings to it. (E.g If you're looking for 'this' in the string 'this and this match; so does this', your array would get [0,9,29].) You might want/need to use regex

With this information (and knowing the length of the substring), you can do something like:
- overlaying a div on the textarea (maybe with contenteditable="true" and z-index: 1)
- enclosing each of the matching strings in a span element (more info about that here) and maybe giving all these spans a class,
- using querySelectorAll to get a NodeList including all of these spans, and
- looping through the list to change their style (so the user can see what is "selected") and their contents (assuming the point was for the user to be able to make changes.)

You can remove the wrapping spans later, update the text area with the updated text, and remove the overlaid div.
Note that your array of indexes probably won't stay valid for long, and depending what you're doing with the initial list, you may want to loop through it backwards to protect these values until you no longer need them.

That's my brainstorming anyway.

Cat
  • 4,141
  • 2
  • 10
  • 18