SHARE
TWEET

Untitled

a guest Aug 22nd, 2019 65 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react"
  2. import PropTypes from "prop-types"
  3. import useMethods from "use-methods"
  4.  
  5. import { findNode, findPos, readRoot } from "./traverseDOM"
  6. import { Title } from "./Title"
  7.  
  8. const contentEditable = {
  9.     contentEditable: true,
  10.     suppressContentEditableWarning: true,
  11.     spellCheck: false
  12. }
  13.  
  14. const initialState = {
  15.     value:     "",
  16.     pos1:      0,
  17.     pos2:      0,
  18.     selection: ""
  19. }
  20.  
  21. const methods = state => ({
  22.     reset(newValue) {
  23.         let { value, pos1, pos2, selection } = state
  24.         value = newValue
  25.         pos1 = state.value.length
  26.         pos2 = state.pos1
  27.         selection = ""
  28.         return { value, pos1, pos2, selection }
  29.     },
  30.     insert(newValue) {
  31.         let { value, pos1, pos2, selection } = state
  32.         value = value.slice(0, pos1) + newValue + value.slice(pos2)
  33.         pos1 = pos1 + newValue.length
  34.         pos2 = pos1
  35.         selection = ""
  36.         return { value, pos1, pos2, selection }
  37.     },
  38.     trimLeft(n) {
  39.         let { value, pos1, pos2, selection } = state
  40.         value = value.slice(0, pos1 + n) + value.slice(pos2)
  41.         pos1 = pos1 + n
  42.         pos2 = pos1
  43.         selection = ""
  44.         return { value, pos1, pos2, selection }
  45.     },
  46.     setPos(newPos1, newPos2 = newPos1) {
  47.         let { value, pos1, pos2, selection } = state
  48.         pos1 = Math.min(newPos1, newPos2) // Get the minimum of `pos1` and `pos2`.
  49.         pos2 = Math.max(newPos1, newPos2) // Get the maximum of `pos1` and `pos2`.
  50.         selection = value.slice(pos1, pos2)
  51.         return { value, pos1, pos2, selection }
  52.     }
  53. })
  54.  
  55. // Based on stackoverflow.com/a/53837442.
  56. function useForceUpdate() {
  57.     const [update, setUpdate] = useState(false)
  58.     const forceUpdate = () => {
  59.         setUpdate(!update)
  60.     }
  61.     return [update, forceUpdate]
  62. }
  63.  
  64. const DEBUG = true
  65.  
  66. function NewNote(props) {
  67.     const ref = useRef(null)
  68.     const [update, forceUpdate] = useForceUpdate()
  69.  
  70.     const [
  71.         { value, pos1, pos2, selection },
  72.         { reset, insert, trimLeft, setPos }
  73.     ] = useMethods(methods, initialState)
  74.  
  75.     useEffect(() => {
  76.         const h = e => {
  77.             if (document.activeElement !== ref.current) {
  78.                 return
  79.             }
  80.             const selection = document.getSelection()
  81.             const pos1 = findPos(ref.current, selection.anchorNode, selection.anchorOffset)
  82.             const pos2 = findPos(ref.current, selection.focusNode , selection.focusOffset )
  83.             setPos(pos1, pos2)
  84.         }
  85.         document.addEventListener("selectionchange", h)
  86.         return () => {
  87.             document.removeEventListener("selectionchange", h)
  88.         }
  89.     }, [])
  90.  
  91.     useLayoutEffect(() => {
  92.         DEBUG && console.log("useLayoutEffect")
  93.  
  94.         const selection = document.getSelection()
  95.         const node1 = findNode(ref.current, pos1)
  96.         const node2 = findNode(ref.current, pos2)
  97.         const range = document.createRange()
  98.         range.setStart(node1.node, node1.offset)
  99.         range.setEnd  (node2.node, node2.offset)
  100.         selection.removeAllRanges()
  101.         selection.addRange(range)
  102.         if (pos1 === pos2 && pos1 === value.length) {
  103.             window.scrollTo(0, ref.current.scrollHeight)
  104.         }
  105.     }, [value, update])
  106.  
  107.     const handleKeyDown = e => {
  108.         switch (e.key) {
  109.             case "Enter":
  110.                 e.preventDefault()
  111.                 insert("\n")
  112.                 return
  113.             case "Backspace":
  114.                 if (!value) {
  115.                     e.preventDefault()
  116.                     reset("")
  117.                     // `forceUpdate` is needed because `reset("")` is
  118.                     // idempotent.
  119.                     forceUpdate()
  120.                 } else if (value[pos1 - 1] === "\n") {
  121.                     e.preventDefault()
  122.                     trimLeft(-1)
  123.                 } else if (selection.length) {
  124.                     e.preventDefault()
  125.                     insert("")
  126.                 }
  127.                 return
  128.  
  129.             default:
  130.                 return
  131.         }
  132.     }
  133.  
  134.     const handleInput = e => {
  135.         if (e.nativeEvent.inputType === "insertCompositionText") {
  136.             return
  137.         }
  138.         const newValue = readRoot(ref.current)
  139.         reset(newValue)
  140.         const selection = document.getSelection()
  141.         const pos1 = findPos(ref.current, selection.anchorNode, selection.anchorOffset)
  142.         const pos2 = findPos(ref.current, selection.focusNode , selection.focusOffset )
  143.         setPos(pos1, pos2)
  144.     }
  145.  
  146.     const handleCut = e => {
  147.         e.preventDefault()
  148.         e.clipboardData.setData("text/plain", selection)
  149.         insert("")
  150.     }
  151.  
  152.     const handleCopy = e => {
  153.         e.preventDefault()
  154.         e.clipboardData.setData("text/plain", selection)
  155.     }
  156.  
  157.     const handlePaste = e => {
  158.         e.preventDefault()
  159.         const data = e.clipboardData.getData("text/plain")
  160.         insert(data)
  161.         // `forceUpdate` is needed because pasting a selection
  162.         // can be idempotent.
  163.         forceUpdate()
  164.     }
  165.  
  166.     return (
  167.         <Title title="Editing …">
  168.             <div className="h:4"/>
  169.             <div className="flex -r -x:center">
  170.                 <div className="w:40">
  171.  
  172.                     <p style={{fontFamily: "monospace"}}>
  173.                         ^{value.split("\n").join("\\n")}$<br />
  174.                         {pos1}, {pos2}
  175.                     </p>
  176.                     <div className="h:1" />
  177.  
  178.                     <div ref={ref} {...contentEditable} onKeyDown={handleKeyDown} onInput={handleInput} onCut={handleCut} onCopy={handleCopy} onPaste={handlePaste}>
  179.                         {value.split("\n").map((block, index) => (
  180.                             <p key={index} className="fs:1.2 ls:-0.0.2 lh:1.3">
  181.                                 {!block ? <span children={<br />} /> : block}
  182.                             </p>
  183.                         ))}
  184.                     </div>
  185.  
  186.                     {/* <div className="h:1" /> */}
  187.                     {/* <textarea className="w:max h:8 fs:1.2 ls:-0.0.2 lh:1.3" /> */}
  188.                 </div>
  189.             </div>
  190.             <div className="h:8" />
  191.         </Title>
  192.     )
  193. }
  194.  
  195. export { NewNote }
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top