可編輯的有序列表

需求就是用js模擬word的有序列表鍵盤操作,那種全選加序號的還沒實現(xiàn)。
提取幾個比較明顯的特征:

  • 列表項回車生成新列表項
  • 數(shù)字前刪除只會刪除數(shù)字,而不會換到上一行
  • 刪除中間列表項會導(dǎo)致后面的重排序

還有很多細節(jié),暫時不討論,光是上面這些,隱藏的東西就夠做了。

思路

  • 可編輯區(qū)域可以用contenteditable處理
  • 序號用屬性+偽類的方式實現(xiàn)
  • 在數(shù)字前刪除只刪除序號,有兩種方法,一是重寫delete的keydown,阻止默認行為,對所有情形進行判斷;二是在列表項最前面加一個隱式字符​,這個charCodeAt()輸出的是8203,而編輯的空格是 ,code是160,后面會用到。
    我是用第二種思路寫的,但我感覺第一種更好,原因是隱式字符對刪除、左右鍵的影響較大,這個實現(xiàn)的版本還沒有對左右鍵進行重寫。
  • enter的keydown要加stop和prevent,完全重寫。主要考慮:
    • 字符中間截斷
    • 序號行回車,如有文本內(nèi)容,新增序號行;否則刪除當(dāng)前序號
    • 非序號行回車新增非序號行
  • enter的keyup不用修飾,只做序號重排處理
  • delete的keydown比較復(fù)雜,prevent需要在內(nèi)部分情況主動調(diào)用。
    • 前面是占位符
      • 如果是序號行,刪除序號,序號重排
      • 非序號行要看上一行的狀態(tài),如果有文本內(nèi)容,刪除當(dāng)前行,光標移到上一行末尾;如果沒有文本內(nèi)容,把當(dāng)前行的內(nèi)容移至上一行,并把光標移至上一行開頭,刪除當(dāng)前行,且都需要阻止默認行為。
    • 還有一種情況,如果通過鍵盤操作越過了占位符,那么前面就什么都沒有了,非序號行默認行為即可,序號行則與前面是占位符的邏輯一樣

Range

這里面多次用到了光標的判斷和設(shè)置,用到的是Range API,不做多介紹,詳細可以參考文檔。
值得一提的是,如果序號行用的是<li>some text</li>的結(jié)構(gòu),設(shè)置光標時需要將textNode作為參數(shù)而不是li。
這點坑了我好久,關(guān)系到光標的位置設(shè)置,li作為rangeContent,其offset范圍只有0~1,而textNode的范圍是0~text.length。

code

只上js部分,html思路里提到過,結(jié)構(gòu):ol > <li class="number" index="1、">&#8203;some text</li>

const isFst = () => {
    return getSelection().getRangeAt(0).startOffset == 1
}

const isZero = () => {
    return getSelection().getRangeAt(0).startOffset == 0
}

export default {
    methods: {
        // 獲取當(dāng)前行
        getCurLi() {
            return $(getSelection().getRangeAt(0).commonAncestorContainer).closest('li')
        },
        // 設(shè)置光標
        setRange(el, isStart = false) {
            let textNode = el.childNodes[0]
            let range = document.createRange()
            range.selectNodeContents(textNode)
            range.collapse()
            // isStart用來處理是否設(shè)置到首部,1是因為前面還有占位符
            if (isStart) {
                range.setStart(textNode, 1)
                range.setEnd(textNode, 1)
            }
            let sel = getSelection()
            sel.removeAllRanges()
            sel.addRange(range)
        },
        enterKeydown() {
            let li = this.getCurLi()
            let offset = getSelection().getRangeAt(0).startOffset
            let text = li.text()
            let slice
            // 中間截斷
            if (isZero) {
                slice = text.substr(1)
                li.text(text.substr(0, 1))
            } else {
                slice = text.substr(offset)
                li.text(text.substr(0, offset))
            }
            // 有文本
            if (text.length > 1 && li.hasClass('number')) {
                let newLi = $(`<li class="number" index="">&#8203;${slice}</li>`).get(0)
                li.after(newLi)
                this.setRange(newLi, true)
            } else {
                // 空文本
                if (li.hasClass('number')) {
                    let preLi = li.prev()
                    li.remove()
                    let newLi = $(`<li>&#8203;</li>`).get(0)
                    preLi.after(newLi)
                    this.setRange(newLi)
                } else {
                    // 非序號行
                    let newLi = $(`<li>&#8203;${slice}</li>`).get(0)
                    li.after(newLi)
                    this.setRange(newLi, true)
                }
            }
        },
        // 設(shè)置序號
        enterKeyup() {
            let lis = $('ol > li.number')
            for (let i = 0; i < lis.length; i++) {
                lis.eq(i).attr('index', `${j + 1}、`)
            }
        },
        deleteKeydown(e) {
            let li = this.getCurLi()

            // 刪到占位 防止第一位是空格
            if (isFst() && li.text()[0].charCodeAt() !== 160) {
                // 序號行刪序號 重排序
                if (li.hasClass('number')) {
                    li.removeClass('number')
                    li.attr('index', '')
                    this.enterKeyup()
                } else {
                    let pre = li.prev()
                    // 前一行有文本
                    if (pre.text().length != 1) {
                        this.setRange(pre.get(0))
                        li.remove()
                        e.preventDefault()
                    } else {
                        // 無文本
                        pre.text(li.text())
                        this.setRange(pre.get(0), true)
                        li.remove()
                        e.preventDefault()
                    }
                }
            } else if (isZero()) {
                if (li.hasClass('number')) {
                    li.removeClass('number')
                    li.attr('index', '')
                    this.enterKeyup()
                    li.text(li.text().substr(1))
                    e.preventDefault()
                }
            }
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容