Repository: marktext/marktext
File: src/muya/lib/contentState/updateCtrl.js (JavaScript)

Code before:
const INLINE_UPDATE_FRAGMENTS = [
  '(?:^|\n) {0,3}([*+-] {1,4})', // Bullet list
  '(?:^|\n)(\\[[x ]{1}\\] {1,4})', // Task list
  '(?:^|\n) {0,3}(\\d{1,9}(?:\\.|\\)) {1,4})', // Order list
  '(?:^|\n) {0,3}(#{1,6})(?=\\s{1,}|$)', // ATX headings
  '^(?:[\\s\\S]+?)\\n {0,3}(\\={3,}|\\-{3,})(?= {1,}|$)', // Setext headings **match from beginning**
  '(?:^|\n) {0,3}(>).+', // Block quote
  '^( {4,})', // Indent code **match from beginning**
  '^(\\[\\^[^\\^\\[\\]\\s]+?(?<!\\\\)\\]: )', // Footnote **match from beginning**
  '(?:^|\n) {0,3}((?:\\* *\\* *\\*|- *- *-|_ *_ *_)[ \\*\\-\\_]*)$' // Thematic break
]

const INLINE_UPDATE_REG = new RegExp(INLINE_UPDATE_FRAGMENTS.join('|'), 'i')

const updateCtrl = ContentState => {
  ContentState.prototype.setCheckBoxState = function (checkbox, checked) {
    checkbox.checked = checked
    const block = this.getBlock(checkbox.id)
    block.checked = checked
    checkbox.classList.toggle(CLASS_OR_ID.AG_CHECKBOX_CHECKED)
  }

  ContentState.prototype.getParentCheckBox = function (checkbox) {
    const parent = checkbox.parentElement.parentElement.parentElement
    if (parent.id !== CLASS_OR_ID.AG_EDITOR_ID) {
      return parent.firstElementChild
    } else {
      return null
    }
  }

  ContentState.prototype.cumputeChecboxStatus = function (parentCheckbox) {
    const children = parentCheckbox.parentElement.lastElementChild.children
    for (let i = 0; i < children.length; i++) {
      const checkbox = children[i].firstElementChild
      if (checkbox.checked === false) {
        return false
      }
    }
    return true
  }

  ContentState.prototype.updateParentsCheckBoxState = function (checkbox) {
    let parent = this.getParentCheckBox(checkbox)
    while (parent !== null) {
      const checked = this.cumputeChecboxStatus(parent)
      if (parent.checked !== checked) {
        this.setCheckBoxState(parent, checked)
        parent = this.getParentCheckBox(parent)
      } else {
        break

Review comment (suggestion):
Are you sure this firstElementChild is `checkbox`? we support put task list in other block like `blockquote`.

Code after:
  '(?:^|\n) {0,3}([*+-] {1,4})', // Bullet list
  '(?:^|\n)(\\[[x ]{1}\\] {1,4})', // Task list
  '(?:^|\n) {0,3}(\\d{1,9}(?:\\.|\\)) {1,4})', // Order list
  '(?:^|\n) {0,3}(#{1,6})(?=\\s{1,}|$)', // ATX headings
  '^(?:[\\s\\S]+?)\\n {0,3}(\\={3,}|\\-{3,})(?= {1,}|$)', // Setext headings **match from beginning**
  '(?:^|\n) {0,3}(>).+', // Block quote
  '^( {4,})', // Indent code **match from beginning**
  '^(\\[\\^[^\\^\\[\\]\\s]+?(?<!\\\\)\\]: )', // Footnote **match from beginning**
  '(?:^|\n) {0,3}((?:\\* *\\* *\\*|- *- *-|_ *_ *_)[ \\*\\-\\_]*)$' // Thematic break
]

const INLINE_UPDATE_REG = new RegExp(INLINE_UPDATE_FRAGMENTS.join('|'), 'i')

const updateCtrl = ContentState => {
  ContentState.prototype.checkSameMarkerOrDelimiter = function (list, markerOrDelimiter) {
    if (!/ol|ul/.test(list.type)) return false
    return list.children[0].bulletMarkerOrDelimiter === markerOrDelimiter
  }

  ContentState.prototype.checkNeedRender = function (cursor = this.cursor) {
    const { labels } = this.stateRender
    const { start: cStart, end: cEnd, anchor, focus } = cursor
    const startBlock = this.getBlock(cStart ? cStart.key : anchor.key)
    const endBlock = this.getBlock(cEnd ? cEnd.key : focus.key)
    const startOffset = cStart ? cStart.offset : anchor.offset
    const endOffset = cEnd ? cEnd.offset : focus.offset
    const NO_NEED_TOKEN_REG = /text|hard_line_break|soft_line_break/

    for (const token of tokenizer(startBlock.text, {
      labels,
      options: this.muya.options
    })) {
      if (NO_NEED_TOKEN_REG.test(token.type)) continue
      const { start, end } = token.range
      const textLen = startBlock.text.length
      if (
        conflict([Math.max(0, start - 1), Math.min(textLen, end + 1)], [startOffset, startOffset])
      ) {
        return true
      }
    }
    for (const token of tokenizer(endBlock.text, {
      labels,
      options: this.muya.options
    })) {
      if (NO_NEED_TOKEN_REG.test(token.type)) continue
      const { start, end } = token.range
      const textLen = endBlock.text.length
      if (
        conflict([Math.max(0, start - 1), Math.min(textLen, end + 1)], [endOffset, endOffset])
      ) {