<div id="intro">
  Welcome to Code Scrubber!<br/>
  The code lower is freely editable, feel free to paste (Ctrl+Shift+F) from wherever <br/>
  Pressing ANALYZE will detect numbers and make them scrubbable. This also happens 3s after last entry, but unfortunately resets cursor position, needs fixing :)<br/>
  Pressing MODE will switch between HTML (direct copy) and JS Eval (JavaScript expression evaluation modes), which you can use to eg. evaluate math expressions (try 1 + 1)<br/><br/>
  <strong>Pressing Ctrl+Shift toggles the scrubbing option</strong><br/>
  Go to <a target="_blank" href="https://codepen.io/domjancik/pen/VwamOyJ">this pen</a> for a hold to edit version
</div>
<div>
  <button id="button">ANALYZE</button>
  <button id="button-mode">MODE</button>
  <div class="inline" id="mode">HTML</div>
</div>
<pre id="code" contenteditable="true">42 + 100</pre>
<div id="progress"></div>
<div id="equals">&nbsp;= </div>
<div class="inline" id="result">RESULT</div>
<div id="footer"><a href="https://domj.net" target="_blank">domj<br/>2020</a></div>
body {
  font-family: sans-serif;
  background: brown;
}

#intro {
  font-size: 0.75rem;
  color: lightyellow;
  margin-bottom: 1rem;
}

#code {
  background: lightyellow;
  padding: 1rem;
  border-radius: 5px;
  margin-bottom: 0;
}

#mode {
  color: white;
}

#equals {
  text-align: center;
  color: white;
  font-size: 2rem;
}

button, button:focus {
  border: none;
  border-radius: 5px;
  background: lightyellow;
  padding: 10px;
  box-shadow: 0 2px black;
  outline-style: none;
  transition: all 100ms;
  font-weight: bold;
}

button:active {
  box-shadow: 0 1px black;
  transform: translateY(1px);
  outline-style: none;
}

#result {
  border: solid lightgray 1px;
  border-radius: 5px;
  background: white;
}

.inline {
  display: inline-block;
}

.number {
  transition: all 500ms;
  color: white;
  background: black;
  padding: 1px;
  border-radius: 5px;
  cursor: e-resize;
  border: 1px solid transparent;
}

.disabled .number {
  background: transparent;
  color: inherit;
  cursor: inherit;
  border: 1px solid rgba(0,0,0,0.1);
}

#footer {
  position: fixed;
  bottom: 20px;
  right: 20px;
  color: white;
}

#progress {
  margin-top: 5px;
  width: 10%;
  height: 1px;
  background: lightyellow;
}

#footer a {
  color: white;
}
const code = $('#code')
const button = $("#button")
const result = $("#result")

let mouseDown = false
let lastX = 0
let curElement = null
let numberElements = null
let mode = false
let scrubbingEnabled = true

const evaluateEval = () => result.text(eval(code.text()))

const evaluateHtml = () => {
  console.log(code.text())
  result.html(code.text())
}

const evaluate = () => {
  mode ? evaluateEval() : evaluateHtml()
}

const analyze = () => {
  let text = code.text()
  text = text.replace(/</g, "&lt;")
  text = text.replace(/(\d+)/g, (a, b) => `<span class="number">${a}</span>`)
  //numbers = text
  console.log(text)
  code.html(text)  
  numberElements = $(".number")
  
  numberElements.each(function(index) {
    const el = $(this)
    
    console.log("num")
    console.log(el.text())

    el.mousedown(e => {
      if (!scrubbingEnabled) return e
      
      lastX = e.clientX
      mouseDown = true
      curElement = el
    })
  })
  
  evaluate()
}

// const numberElement = $(numberElements.get(0))
// const pos = numberElement.position()

// pos.top += numberElement.height()


const scrub = (e) => {
  if (!mouseDown) return
  
  // console.log(e)
  
  console.log("scrubbing")
  let num = +curElement.text()
  curElement.text(num + e.clientX - lastX)
  
  lastX = e.clientX
  
  evaluate()
} 

$(document)
  .mouseup(() => {
    mouseDown = false
  })
  .mousemove(e => scrub(e))


button.click(analyze)
$("#button-mode").click(() => {
  mode = !mode
  $("#mode").text(mode ? "JS eval()" : "HTML")
})

code.text(`<svg width="100" height="100">
  <circle cx="50" cy="50" r="40" stroke="green" stroke-width="4" fill="yellow" />
</svg>`)

// Keyboard interaction
$(document).keydown(e => {
  if (e.code.indexOf('Shift') !== -1 && e.ctrlKey) {
    scrubbingEnabled = !scrubbingEnabled
    code.toggleClass('disabled', !scrubbingEnabled)
  }
})


// Auto Analyze
const progressBar = $("#progress")
let progress = 0;
let lastChange = new Date()
const ANALYZE_DELAY = 3000

code.on('input', _.debounce(analyze, ANALYZE_DELAY))
code.on('input', () => {lastChange = new Date()})

const progressUpdate = () => {
  const elapsed = new Date() - lastChange
  const percent = Math.min(elapsed / ANALYZE_DELAY * 100, 100)
  progressBar.css('width', `${percent}%`)
  window.requestAnimationFrame(progressUpdate)
}

window.requestAnimationFrame(progressUpdate)


// Init
analyze()

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js