import { escape, unescape } from './utils'
/**
 * Renderer
 */

function Renderer (options = {}) {
  this.options = options
}

Renderer.prototype.frontmatter = function (text) {
  return `<pre class="front-matter">\n${text}</pre>\n`
}

Renderer.prototype.multiplemath = function (text) {
  return `<pre class="multiple-math">\n${text}</pre>\n`
}

Renderer.prototype.code = function (code, lang, escaped, codeBlockStyle) {
  if (this.options.highlight) {
    let out = this.options.highlight(code, lang)
    if (out !== null && out !== code) {
      escaped = true
      code = out
    }
  }

  let className = codeBlockStyle === 'fenced' ? 'fenced-code-block' : 'indented-code-block'
  className = lang ? `${className} ${this.options.langPrefix}${escape(lang, true)}` : className

  return '
' +
    (escaped ? code : escape(code, true)) +
    '\n
\n'
}

Renderer.prototype.blockquote = function (quote) {
  return '
\n' + quote + '
\n'
}

Renderer.prototype.html = function (html) {
  return html
}

Renderer.prototype.heading = function (text, level, raw, headingStyle) {
  return '<h' +
    level +
    ' id="' +
    this.options.headerPrefix +
    raw.toLowerCase().replace(/[^\w]+/g, '-') +
    '" class="' +
    headingStyle +
    '">' +
    text +
    '</h' +
    level +
    '>\n'
}

Renderer.prototype.hr = function () {
  return this.options.xhtml ? '
\n' : '
\n'
}

Renderer.prototype.list = function (body, ordered, start, taskList) {
  const type = ordered ? 'ol' : 'ul'
  const classes = !ordered ? (taskList ? ' class="task-list"' : ' class="bullet-list"') : ' class="order-list"'
  const startatt = (ordered && start !== 1) ? (' start="' + start + '"') : ''
  return '<' + type + classes + startatt + '>\n' + body + '</' + type + '>\n'
}

Renderer.prototype.listitem = function (text, checked, listItemType, bulletListItemMarker, loose) {
  let classes
  switch (listItemType) {
    case 'order':
      classes = ' class="order-list-item'
      break
    case 'task':
      classes = ' class="task-list-item'
      break
    case 'bullet':
      classes = ' class="bullet-list-item'
      break
    default:
      throw new Error('Invalid state')
  }

  // "tight-list-item" is only used to remove
 padding
  classes += loose ? ` loose-list-item"` : ` tight-list-item"`

  if (checked === undefined) {
    return '
' + text + '
\n'
  }

  return '
' +
    '<input type="checkbox" class="task-list-item-checkbox"' +
    (checked ? ' checked' : '') +
    '> ' +
    text +
    '
\n'
}

Renderer.prototype.paragraph = function (text) {
  return '
' + text + '
\n'
}

Renderer.prototype.table = function (header, body) {
  return '
\n' +
    '
\n' +
    header +
    '
\n' +
    '
\n' +
    body +
    '
\n' +
    '
\n'
}

Renderer.prototype.tablerow = function (content) {
  return '
\n' + content + '
\n'
}

Renderer.prototype.tablecell = function (content, flags) {
  const type = flags.header ? 'th' : 'td'
  const tag = flags.align
    ? '<' + type + ' style="text-align:' + flags.align + '">'
    : '<' + type + '>'
  return tag + content + '</' + type + '>\n'
}

// span level renderer
Renderer.prototype.strong = function (text) {
  return '' + text + ''
}

Renderer.prototype.em = function (text) {
  return '' + text + ''
}

Renderer.prototype.codespan = function (text) {
  return '' + text + ''
}

Renderer.prototype.br = function () {
  return this.options.xhtml ? '
' : '
'
}

Renderer.prototype.del = function (text) {
  return '' + text + ''
}

Renderer.prototype.link = function (href, title, text) {
  if (this.options.sanitize) {
    let prot
    try {
      prot = decodeURIComponent(unescape(href))
        .replace(/[^\w:]/g, '')
        .toLowerCase()
    } catch (e) {
      return ''
    }
    if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0) {
      return ''
    }
  }
  let out = '' + text + ''
  return out
}

Renderer.prototype.image = function (href, title, text) {
  let out = '' : '>'
  return out
}

Renderer.prototype.text = function (text) {
  return text
}

export default Renderer


if (show && (event.key === EVENT_KEYS.ArrowUp || event.key === EVENT_KEYS.ArrowDown)) {

if ((event.key === EVENT_KEYS.ArrowUp && topOffset > 0) || (event.key === EVENT_KEYS.ArrowDown && bottomOffset > 0)) {

import Renderer from './renderer'
import { normal, breaks, gfm, pedantic } from './inlineRules'
import { escape } from './utils'
/**
 * Inline Lexer & Compiler
 */

function InlineLexer (links, options) {
  this.options = options
  this.links = links
  this.rules = normal
  this.renderer = this.options.renderer || new Renderer()
  this.renderer.options = this.options

  if (!this.links) {
    throw new Error('Tokens array requires a `links` property.')
  }

  if (this.options.gfm) {
    if (this.options.breaks) {
      this.rules = breaks
    } else {
      this.rules = gfm
    }
  } else if (this.options.pedantic) {
    this.rules = pedantic
  }
}

/**
 * Lexing/Compiling
 */

InlineLexer.prototype.output = function (src) {
  const { disableInline } = this.options
  if (disableInline) {
    return escape(src)
  }

  let out = ''
  let link
  let text
  let href
  let cap

  while (src) {
    // escape
    cap = this.rules.escape.exec(src)
    if (cap) {
      src = src.substring(cap[0].length)
      out += cap[1]
      continue
    }

    // autolink
    cap = this.rules.autolink.exec(src)
    if (cap) {
      src = src.substring(cap[0].length)
      if (cap[2] === '@') {
        text = cap[1].charAt(6) === ':' ? this.mangle(cap[1].substring(7)) : this.mangle(cap[1])
        href = this.mangle('mailto:') + text
      } else {
        text = escape(cap[1])
        href = text
      }
      out += this.renderer.link(href, null, text)
      continue
    }

    // url (gfm)
    cap = this.rules.url.exec(src)
    if (!this.inLink && cap) {
      src = src.substring(cap[0].length)
      text = escape(cap[1])
      href = text
      out += this.renderer.link(href, null, text)
      continue
    }

    // tag
    cap = this.rules.tag.exec(src)
    if (cap) {
      if (!this.inLink && /^<a /i.test(cap[0])) {
        this.inLink = true
      } else if (this.inLink && /^<\/a>/i.test(cap[0])) {
        this.inLink = false
      }
      src = src.substring(cap[0].length)
      out += this.options.sanitize
        ? this.options.sanitizer
          ? this.options.sanitizer(cap[0])
          : escape(cap[0])
        : cap[0]
      continue
    }

    // link
    cap = this.rules.link.exec(src)
    if (cap) {
      src = src.substring(cap[0].length)
      this.inLink = true
      out += this.outputLink(cap, {
        href: cap[2],
        title: cap[3]
      })
      this.inLink = false
      continue
    }

    // reflink, nolink
    cap = this.rules.reflink.exec(src) || this.rules.nolink.exec(src)
    if (cap) {
      src = src.substring(cap[0].length)
      link = (cap[2] || cap[1]).replace(/\s+/g, ' ')
      link = this.links[link.toLowerCase()]
      if (!link || !link.href) {
        out += cap[0].charAt(0)
        src = cap[0].substring(1) + src
        continue
      }
      this.inLink = true
      out += this.outputLink(cap, link)
      this.inLink = false
      continue
    }

    // strong
    cap = this.rules.strong.exec(src)
    if (cap) {
      src = src.substring(cap[0].length)
      out += this.renderer.strong(this.output(cap[2] || cap[1]))
      continue
    }

    // em
    cap = this.rules.em.exec(src)
    if (cap) {
      src = src.substring(cap[0].length)
      out += this.renderer.em(this.output(cap[2] || cap[1]))
      continue
    }

    // code
    cap = this.rules.code.exec(src)
    if (cap) {
      src = src.substring(cap[0].length)
      out += this.renderer.codespan(escape(cap[2], true))
      continue
    }

    // br
    cap = this.rules.br.exec(src)
    if (cap) {
      src = src.substring(cap[0].length)
      out += this.renderer.br()
      continue
    }

    // del (gfm)
    cap = this.rules.del.exec(src)
    if (cap) {
      src = src.substring(cap[0].length)
      out += this.renderer.del(this.output(cap[1]))
      continue
    }

    // text
    cap = this.rules.text.exec(src)
    if (cap) {
      src = src.substring(cap[0].length)
      out += this.renderer.text(escape(this.smartypants(cap[0])))
      continue
    }

    if (src) {
      throw new Error('Infinite loop on byte: ' + src.charCodeAt(0))
    }
  }

  return out
}

/**
 * Compile Link
 */

InlineLexer.prototype.outputLink = function (cap, link) {
  const href = escape(link.href)
  const title = link.title ? escape(link.title) : null

  return cap[0].charAt(0) !== '!'
    ? this.renderer.link(href, title, this.output(cap[1]))
    : this.renderer.image(href, title, escape(cap[1]))
}

/**
 * Smartypants Transformations
 */
/* eslint-disable no-useless-escape */
InlineLexer.prototype.smartypants = function (text) {
  if (!this.options.smartypants) return text
  return text
    // em-dashes
    .replace(/---/g, '\u2014')
    // en-dashes
    .replace(/--/g, '\u2013')
    // opening singles
    .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018')
    // closing singles & apostrophes
    .replace(/'/g, '\u2019')
    // opening doubles
    .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c')
    // closing doubles
    .replace(/"/g, '\u201d')
    // ellipses
    .replace(/\.{3}/g, '\u2026')
}
/* eslint-ensable no-useless-escape */
/**
 * Mangle Links
 */

InlineLexer.prototype.mangle = function (text) {
  if (!this.options.mangle) return text
  const l = text.length
  let out = ''
  let i
  let ch

  for (i = 0; i < l; i++) {
    ch = text.charCodeAt(i)
    if (Math.random() > 0.5) {
      ch = 'x' + ch.toString(16)
    }
    out += '&#' + ch + ';'
  }

  return out
}

export default InlineLexer


<template>
  <div
    :title="file.pathname"
    class="side-bar-file"
    :style="{'padding-left': `${depth * 5 + 15}px`, 'opacity': file.isMarkdown ? 1 : 0.75 }"
    @click="handleFileClick()"
    :class="[{'current': currentFile.pathname === file.pathname, 'active': file.id === activeItem.id }, theme]"
    ref="file"
  >
    <file-icon
      :name="file.name"
    ></file-icon>
    <input
      type="text"
      @click.stop="noop"
      class="rename"
      v-if="renameCache === file.pathname"
      v-model="newName"
      ref="renameInput"
      @keydown.enter="rename"
    >
    <span v-else>{{ file.name }}</span>
  </div>
</template>

<script>
  import FileIcon from './icon.vue'
  import { mapState } from 'vuex'
  import { fileMixins } from '../../mixins'
  import { showContextMenu } from '../../contextMenu/sideBar'
  import bus from '../../bus'

  export default {
    mixins: [fileMixins],
    name: 'file',
    data () {
      return {
        newName: ''
      }
    },
    props: {
      file: {
        type: Object,
        required: true
      },
      depth: {
        type: Number,
        required: true
      }
    },
    components: {
      FileIcon
    },
    computed: {
      ...mapState({
        'theme': state => state.preferences.theme,
        'renameCache': state => state.project.renameCache,
        'activeItem': state => state.project.activeItem,
        'clipboard': state => state.project.clipboard,
        'currentFile': state => state.editor.currentFile,
        'tabs': state => state.editor.tabs
      })
    },
    created () {
      this.$nextTick(() => {
        this.$refs.file.addEventListener('contextmenu', event => {
          event.preventDefault()
          this.$store.dispatch('CHANGE_ACTIVE_ITEM', this.file)
          showContextMenu(event, !!this.clipboard)
        })

        bus.$on('SIDEBAR::show-rename-input', this.focusRenameInput)
      })
    },
    methods: {
      noop () {},
      focusRenameInput () {
        this.$nextTick(() => {
          if (this.$refs.renameInput) {
            this.$refs.renameInput.focus()
            this.newName = this.file.name
          }
        })
      },
      rename () {
        const { newName } = this
        if (newName) {
          this.$store.dispatch('RENAME_IN_SIDEBAR', newName)
        }
      }
    }
  }
</script>

<style scoped>
  .side-bar-file {
    display: flex;
    position: relative;
    align-items: center;
    cursor: default;
    user-select: none;
    height: 30px;
    box-sizing: border-box;
    padding-right: 15px;
    &:hover {
      background: var(--extraLightBorder);
    }
    & > span {
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
    &::before {
      content: '';
      position: absolute;
      display: block;
      left: 0;
      background: var(--activeColor);
      width: 2px;
      height: 0;
      top: 50%;
      transform: translateY(-50%);
      transition: all .2s ease;
    }
  }
  .side-bar-file.current::before {
    height: 100%;
  }
  .side-bar-file.active {
    background: var(--lightBorder);
  }
  input.rename {
    height: 22px;
    outline: none;
    margin: 5px 0;
    border: 1px solid var(--lightBorder);
    width: 100%;
    border-radius: 3px;
  }
  .dark.side-bar-file:hover {
    background: var(--darkHoverColor);
    color: var(--lightTabColor);
  }
</style>


return { start: rawCursor.end, end: rawCursor.start }

// topOffset is the line counts above cursor, and bottomOffset is line counts bellow cursor.

const { height, top } = paragraph.getBoundingClientRect()

const lineHeight = parseFloat(getComputedStyle(paragraph).lineHeight)

const topOffset = Math.round((y - top) / lineHeight)

const bottomOffset = Math.round((top + height - lineHeight - y) / lineHeight)

import { EVENT_KEYS, CLASS_OR_ID } from '../config'
import { isCursorAtFirstLine, isCursorAtLastLine, isCursorAtBegin, isCursorAtEnd, getBeginPosition, getEndPosition } from '../codeMirror'
import { findNearestParagraph } from '../utils/domManipulate'
import selection from '../selection'

const arrowCtrl = ContentState => {
  ContentState.prototype.findNextRowCell = function (cell) {
    if (!/th|td/.test(cell.type)) throw new Error(`block with type ${cell && cell.type} is not a table cell`)
    const row = this.getParent(cell)
    const rowContainer = this.getParent(row) // thead or tbody
    const column = row.children.indexOf(cell)
    if (rowContainer.type === 'thead') {
      const tbody = this.getNextSibling(rowContainer)
      if (tbody && tbody.children.length) {
        return tbody.children[0].children[column]
      }
    } else if (rowContainer.type === 'tbody') {
      const nextRow = this.getNextSibling(row)
      if (nextRow) {
        return nextRow.children[column]
      }
    }
    return null
  }

  ContentState.prototype.findPrevRowCell = function (cell) {
    if (!/th|td/.test(cell.type)) throw new Error(`block with type ${cell && cell.type} is not a table cell`)
    const row = this.getParent(cell)
    const rowContainer = this.getParent(row) // thead or tbody
    const rowIndex = rowContainer.children.indexOf(row)
    const column = row.children.indexOf(cell)
    if (rowContainer.type === 'tbody') {
      if (rowIndex === 0 && rowContainer.preSibling) {
        const thead = this.getPreSibling(rowContainer)
        return thead.children[0].children[column]
      } else if (rowIndex > 0) {
        return this.getPreSibling(row).children[column]
      }
      return null
    }
    return null
  }

  ContentState.prototype.arrowHandler = function (event) {
    // when the float box is show, use up and down to select item.
    const { floatBox } = this
    const { list, index, show } = floatBox
    const node = selection.getSelectionStart()
    const paragraph = findNearestParagraph(node)
    const id = paragraph.id
    const block = this.getBlock(id)
    const preBlock = this.findPreBlockInLocation(block)
    const nextBlock = this.findNextBlockInLocation(block)

    const { left, right } = selection.getCaretOffsets(paragraph)
    const { start, end } = selection.getCursorRange()

    // fix #101
    if (event.key === EVENT_KEYS.ArrowRight && node && node.classList && node.classList.contains(CLASS_OR_ID['AG_MATH_TEXT'])) {
      const { right } = selection.getCaretOffsets(node)
      if (right === 0 && start.key === end.key && start.offset === end.offset) {
        // It's not recommended to use such lower API, but it's work well.
        return selection.select(node.parentNode.nextElementSibling, 0)
      }
    }

    if (
      (start.key === end.key && start.offset !== end.offset) ||
      start.key !== end.key
    ) {
      return
    }

    if (show && (event.key === EVENT_KEYS.ArrowUp || event.key === EVENT_KEYS.ArrowDown)) {
      event.preventDefault()
      event.stopPropagation()
      switch (event.key) {
        case EVENT_KEYS.ArrowDown:
          if (index < list.length - 1) {
            floatBox.setOptions(list, index + 1)
          }
          break
        case EVENT_KEYS.ArrowUp:
          if (index > 0) {
            floatBox.setOptions(list, index - 1)
          }
          break
      }
      return
    }

    // handle `html` and `code` block when press arrow key
    if (block.type === 'pre' && /code|html/.test(block.functionType)) {
      // handle cursor in code block. the case at firstline or lastline.
      const cm = this.codeBlocks.get(id)
      const anchorBlock = block.functionType === 'html' ? this.getParent(this.getParent(block)) : block
      let activeBlock
      event.preventDefault()
      event.stopPropagation()
      switch (event.key) {
        case EVENT_KEYS.ArrowLeft: // fallthrough
        case EVENT_KEYS.ArrowUp:
          if (
            (event.key === EVENT_KEYS.ArrowUp && isCursorAtFirstLine(cm) && preBlock) ||
            (event.key === EVENT_KEYS.ArrowLeft && isCursorAtBegin(cm) && preBlock)
          ) {
            activeBlock = preBlock
          }
          break
        case EVENT_KEYS.ArrowRight: // fallthrough
        case EVENT_KEYS.ArrowDown:
          if (
            (event.key === EVENT_KEYS.ArrowDown && isCursorAtLastLine(cm)) ||
            (event.key === EVENT_KEYS.ArrowRight && isCursorAtEnd(cm))
          ) {
            if (nextBlock) {
              activeBlock = nextBlock
            } else {
              activeBlock = this.createBlockP()
              this.insertAfter(activeBlock, anchorBlock)
            }
          }
          break
      }

      if (activeBlock) {
        const cursorBlock = activeBlock.type === 'p' ? activeBlock.children[0] : activeBlock
        const offset = cursorBlock.text.length
        const key = cursorBlock.key
        this.cursor = {
          start: {
            key,
            offset
          },
          end: {
            key,
            offset
          }
        }

        return this.partialRender()
      }
      return
    }

    if (/th|td/.test(block.type)) {
      let activeBlock
      const cellInNextRow = this.findNextRowCell(block)
      const cellInPrevRow = this.findPrevRowCell(block)

      if (event.key === EVENT_KEYS.ArrowUp && cellInPrevRow) {
        activeBlock = cellInPrevRow
      }

      if (event.key === EVENT_KEYS.ArrowDown && cellInNextRow) {
        activeBlock = cellInNextRow
      }

      if (activeBlock) {
        event.preventDefault()
        event.stopPropagation()
        const offset = activeBlock.type === 'p'
          ? 0
          : (event.key === EVENT_KEYS.ArrowUp
            ? activeBlock.text.length
            : 0)

        const key = activeBlock.type === 'p'
          ? activeBlock.children[0].key
          : activeBlock.key

        this.cursor = {
          start: {
            key,
            offset
          },
          end: {
            key,
            offset
          }
        }

        return this.partialRender()
      }
    }

    if (
      (preBlock && preBlock.type === 'pre' && /code|html/.test(preBlock.functionType) && event.key === EVENT_KEYS.ArrowUp) ||
      (preBlock && preBlock.type === 'pre' && /code|html/.test(preBlock.functionType) && event.key === EVENT_KEYS.ArrowLeft && left === 0)
    ) {
      event.preventDefault()
      event.stopPropagation()
      const key = preBlock.key
      const offset = 0
      this.cursor = {
        start: { key, offset },
        end: { key, offset }
      }

      const cm = this.codeBlocks.get(preBlock.key)
      preBlock.selection = getEndPosition(cm)

      return this.partialRender()
    } else if (
      (nextBlock && nextBlock.type === 'pre' && /code|html/.test(nextBlock.functionType) && event.key === EVENT_KEYS.ArrowDown) ||
      (nextBlock && nextBlock.type === 'pre' && /code|html/.test(nextBlock.functionType) && event.key === EVENT_KEYS.ArrowRight && right === 0)
    ) {
      event.preventDefault()
      event.stopPropagation()
      const key = nextBlock.key
      const offset = 0
      this.cursor = {
        start: { key, offset },
        end: { key, offset }
      }

      nextBlock.selection = getBeginPosition()

      return this.partialRender()
    } else if (
      (event.key === EVENT_KEYS.ArrowUp) ||
      (event.key === EVENT_KEYS.ArrowLeft && start.offset === 0)
    ) {
      event.preventDefault()
      event.stopPropagation()
      if (!preBlock) return
      const key = preBlock.key
      const offset = preBlock.text.length
      this.cursor = {
        start: { key, offset },
        end: { key, offset }
      }

      return this.partialRender()
    } else if (
      (event.key === EVENT_KEYS.ArrowDown) ||
      (event.key === EVENT_KEYS.ArrowRight && start.offset === block.text.length)
    ) {
      event.preventDefault()
      event.stopPropagation()
      let key
      let newBlock
      if (nextBlock) {
        key = nextBlock.key
      } else {
        newBlock = this.createBlockP()
        const lastBlock = this.blocks[this.blocks.length - 1]
        this.insertAfter(newBlock, lastBlock)
        key = newBlock.children[0].key
      }
      const offset = 0
      this.cursor = {
        start: { key, offset },
        end: { key, offset }
      }

      return this.partialRender()
    }
  }
}

export default arrowCtrl


import { ipcRenderer } from 'electron'
import { error, message } from '../notice'

const state = {}

const getters = {}

const mutations = {
}

const actions = {
  LISTEN_FOR_NOTIFICATION ({ commit }) {
    ipcRenderer.on('AGANI::show-error-notification', (e, msg) => {
      error(msg)
    })
    ipcRenderer.on('AGANI::show-info-notification', (e, { msg, timeout }) => {
      message(msg, timeout)
    })
  }
}

export default { state, getters, mutations, actions }


import Renderer from './renderer'
import Lexer from './lexer'
import Parser from './parser'

const defaults = {
  gfm: true,
  tables: true,
  breaks: false,
  pedantic: false,
  sanitize: false,
  sanitizer: null,
  mangle: true,
  smartLists: false,
  silent: false,
  highlight: null,
  langPrefix: 'lang-',
  smartypants: false,
  headerPrefix: '',
  renderer: new Renderer(),
  xhtml: false,
  disableInline: false
}
/**
 * Marked
 */

function marked (src, opt = {}) {
  try {
    opt = Object.assign({}, defaults, opt)
    return new Parser(opt).parse(new Lexer(opt).lex(src))
  } catch (e) {
    e.message += '\nPlease report this to https://github.com/marktext/marktext/issues.'
    if (opt.silent) {
      return '
An error occurred:

' +
        escape(e.message + '', true) +
        '
'
    }
    throw e
  }
}

export {
  Renderer, Lexer, Parser
}

export default marked


import selection from '../selection'

const tabCtrl = ContentState => {
  ContentState.prototype.findNextCell = function (block) {
    if (!(/td|th/.test(block.type))) throw new Error('only th and td can have next cell')
    const nextSibling = this.getBlock(block.nextSibling)
    const parent = this.getBlock(block.parent)
    const tbOrTh = this.getBlock(parent.parent)
    if (nextSibling) {
      return nextSibling
    } else {
      if (parent.nextSibling) {
        const nextRow = this.getBlock(parent.nextSibling)
        return nextRow.children[0]
      } else if (tbOrTh.type === 'thead') {
        const tBody = this.getBlock(tbOrTh.nextSibling)
        if (tBody.children.length) {
          return tBody.children[0].children[0]
        }
      }
    }
    return false
  }

  ContentState.prototype.isUnindentableListItem = function (block) {
    const parent = this.getParent(block)
    const listItem = this.getParent(parent)
    const list = this.getParent(listItem)
    const listParent = this.getParent(list)
    if (!this.isCollapse()) return false
    if (listParent && listParent.type === 'li') {
      return !list.preSibling ? 'REPLACEMENT' : 'INDENT'
    }
    return false
  }

  ContentState.prototype.isIndentableListItem = function () {
    const { start, end } = this.cursor
    const startBlock = this.getBlock(start.key)
    const parent = this.getParent(startBlock)
    if (parent.type !== 'p' || !parent.parent) {
      return false
    }

    const listItem = this.getParent(parent)
    if (listItem.type !== 'li' || start.key !== end.key || start.offset !== end.offset) {
      return false
    }

    // Now we know it's a list item. Check whether we can indent the list item.
    const list = this.getParent(listItem)
    return list && /ol|ul/.test(list.type) && listItem.preSibling
  }

  ContentState.prototype.unindentListItem = function (block, type) {
    const pBlock = this.getParent(block)
    const listItem = this.getParent(pBlock)
    const list = this.getParent(listItem)
    const listParent = this.getParent(list)
    if (type === 'REPLACEMENT') {
      this.insertBefore(pBlock, list)
      if (this.isOnlyChild(listItem)) {
        this.removeBlock(list)
      } else {
        this.removeBlock(listItem)
      }
    } else if (type === 'INDENT') {
      if (list.children.length === 1) {
        this.removeBlock(list)
      } else {
        const newList = this.createBlock(list.type)
        let target = this.getNextSibling(listItem)
        while (target) {
          this.appendChild(newList, target)
          let temp = target
          target = this.getNextSibling(target)
          this.removeBlock(temp, list)
        }

        if (newList.children.length) this.appendChild(listItem, newList)
        this.removeBlock(listItem, list)
        if (!list.children.length) {
          this.removeBlock(list)
        }
      }
      this.insertAfter(listItem, listParent)
      let target = this.getNextSibling(list)
      while (target) {
        this.appendChild(listItem, target)
        this.removeBlock(target, listParent)
        target = this.getNextSibling(target)
      }
    }

    return this.partialRender()
  }

  ContentState.prototype.indentListItem = function () {
    const { start } = this.cursor
    const startBlock = this.getBlock(start.key)
    const parent = this.getParent(startBlock)
    const listItem = this.getParent(parent)
    const list = this.getParent(listItem)
    const prevListItem = this.getPreSibling(listItem)

    this.removeBlock(listItem)

    // Search for a list in previous block
    let newList = this.getLastChild(prevListItem)
    if (!newList || !/ol|ul/.test(newList.type)) {
      newList = this.createBlock(list.type)
      this.appendChild(prevListItem, newList)
    }

    // Update item type only if we insert into an existing list
    if (newList.children.length !== 0) {
      listItem.isLooseListItem = newList.children[0].isLooseListItem
    }

    this.appendChild(newList, listItem)
    return this.partialRender()
  }

  ContentState.prototype.insertTab = function () {
    const tabSize = this.tabSize
    const tabCharacter = ' '.repeat(tabSize)
    const { start, end } = this.cursor
    const startBlock = this.getBlock(start.key)
    const endBlock = this.getBlock(end.key)
    if (start.key === end.key && start.offset === end.offset) {
      startBlock.text = startBlock.text.substring(0, start.offset) +
        tabCharacter + endBlock.text.substring(end.offset)
      const key = start.key
      const offset = start.offset + tabCharacter.length
      this.cursor = {
        start: { key, offset },
        end: { key, offset }
      }
      return this.partialRender()
    }
  }

  ContentState.prototype.tabHandler = function (event) {
    // disable tab focus
    event.preventDefault()

    const { start, end } = selection.getCursorRange()
    const startBlock = this.getBlock(start.key)
    const endBlock = this.getBlock(end.key)

    if (event.shiftKey) {
      const unindentType = this.isUnindentableListItem(startBlock)
      if (unindentType) {
        this.unindentListItem(startBlock, unindentType)
      }
      return
    }

    let nextCell
    if (start.key === end.key && /th|td/.test(startBlock.type)) {
      nextCell = this.findNextCell(startBlock)
    } else if (/th|td/.test(endBlock.type)) {
      nextCell = endBlock
    }
    if (nextCell) {
      const key = nextCell.key
      const textLength = nextCell.text.length
      this.cursor = {
        start: { key, offset: 0 },
        end: { key, offset: textLength }
      }

      return this.partialRender()
    }

    if (this.isIndentableListItem()) {
      return this.indentListItem()
    }
    return this.insertTab()
  }
}

export default tabCtrl


{
"dependencies": {
"mongodb": {
"version": "1.2.13",
"dependencies": {
"bson": {
"version": "0.1.8"


Package.describe({
  summary: "Make HTTP calls to remote servers"
});

Package.on_use(function (api) {
  api.add_files('httpcall_common.js', ['client', 'server']);
  api.add_files('httpcall_client.js', 'client');
  api.add_files('httpcall_server.js', 'server');
});

Package.on_test(function (api) {
  api.use('jquery', 'client');
  api.use('http', ['client', 'server']);
  api.use('test-helpers', ['client', 'server']);

  api.add_files('test_responder.js', 'server');
  api.add_files('httpcall_tests.js', ['client', 'server']);
});


Random = {};

// see http://baagoe.org/en/wiki/Better_random_numbers_for_javascript
// for a full discussion and Alea implementation.
Random._Alea = function () {
  function Mash() {
    var n = 0xefc8249d;

    var mash = function(data) {
      data = data.toString();
      for (var i = 0; i < data.length; i++) {
        n += data.charCodeAt(i);
        var h = 0.02519603282416938 * n;
        n = h >>> 0;
        h -= n;
        h *= n;
        n = h >>> 0;
        h -= n;
        n += h * 0x100000000; // 2^32
      }
      return (n >>> 0) * 2.3283064365386963e-10; // 2^-32
    };

    mash.version = 'Mash 0.9';
    return mash;
  }

  return (function (args) {
    var s0 = 0;
    var s1 = 0;
    var s2 = 0;
    var c = 1;

    if (args.length == 0) {
      args = [+new Date];
    }
    var mash = Mash();
    s0 = mash(' ');
    s1 = mash(' ');
    s2 = mash(' ');

    for (var i = 0; i < args.length; i++) {
      s0 -= mash(args[i]);
      if (s0 < 0) {
        s0 += 1;
      }
      s1 -= mash(args[i]);
      if (s1 < 0) {
        s1 += 1;
      }
      s2 -= mash(args[i]);
      if (s2 < 0) {
        s2 += 1;
      }
    }
    mash = null;

    var random = function() {
      var t = 2091639 * s0 + c * 2.3283064365386963e-10; // 2^-32
      s0 = s1;
      s1 = s2;
      return s2 = t - (c = t | 0);
    };
    random.uint32 = function() {
      return random() * 0x100000000; // 2^32
    };
    random.fract53 = function() {
      return random() +
        (random() * 0x200000 | 0) * 1.1102230246251565e-16; // 2^-53
    };
    random.version = 'Alea 0.9';
    random.args = args;
    return random;

  } (Array.prototype.slice.call(arguments)));
};

// instantiate RNG.  Heuristically collect entropy from various sources

// client sources
var height = (typeof window !== 'undefined' && window.innerHeight) ||
      (typeof document !== 'undefined'
       && document.documentElement
       && document.documentElement.clientHeight) ||
      (typeof document !== 'undefined'
       && document.body
       && document.body.clientHeight) ||
      1;

var width = (typeof window !== 'undefined' && window.innerWidth) ||
      (typeof document !== 'undefined'
       && document.documentElement
       && document.documentElement.clientWidth) ||
      (typeof document !== 'undefined'
       && document.body
       && document.body.clientWidth) ||
      1;

var agent = (typeof navigator !== 'undefined' && navigator.userAgent) || "";

// server sources
var pid = (typeof process !== 'undefined' && process.pid) || 1;

// XXX On the server, use the crypto module (OpenSSL) instead of this PRNG.
//     (Make Random.fraction be generated from Random.hexString instead of the
//     other way around, and generate Random.hexString from crypto.randomBytes.)
Random.fraction = new Random._Alea([
  new Date(), height, width, agent, pid, Math.random()]);

Random.choice = function (arrayOrString) {
  var index = Math.floor(Random.fraction() * arrayOrString.length);
  if (typeof arrayOrString === "string")
    return arrayOrString.substr(index, 1);
  else
    return arrayOrString[index];
};

var UNMISTAKABLE_CHARS = "23456789ABCDEFGHJKLMNPQRSTWXYZabcdefghijkmnopqrstuvwxyz";
Random.id = function() {
  var digits = [];
  // Length of 17 preserves around 96 bits of entropy, which is the
  // amount of state in our PRNG
  for (var i = 0; i < 17; i++) {
    digits[i] = Random.choice(UNMISTAKABLE_CHARS);
  }
  return digits.join("");
};

var HEX_DIGITS = "0123456789abcdef";
Random.hexString = function (digits) {
  var hexDigits = [];
  for (var i = 0; i < digits; ++i) {
    hexDigits.push(Random.choice("0123456789abcdef"));
  }
  return hexDigits.join('');
};