Example #1
0
  publishMarkdownNow () {
    const {selectedNoteKeys} = this.state
    const notes = this.notes.map((note) => Object.assign({}, note))
    const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
    const firstNote = selectedNotes[0]
    const config = ConfigManager.get()
    const {address, token, authMethod, username, password} = config.blog
    let authToken = ''
    if (authMethod === 'USER') {
      authToken = `Basic ${window.btoa(`${username}:${password}`)}`
    } else {
      authToken = `Bearer ${token}`
    }
    const contentToRender = firstNote.content.replace(`# ${firstNote.title}`, '')
    const markdown = new Markdown()
    const data = {
      title: firstNote.title,
      content: markdown.render(contentToRender),
      status: 'publish'
    }

    let url = ''
    let method = ''
    if (firstNote.blog && firstNote.blog.blogId) {
      url = `${address}${WP_POST_PATH}/${firstNote.blog.blogId}`
      method = 'PUT'
    } else {
      url = `${address}${WP_POST_PATH}`
      method = 'POST'
    }
    // eslint-disable-next-line no-undef
    fetch(url, {
      method: method,
      body: JSON.stringify(data),
      headers: {
        'Authorization': authToken,
        'Content-Type': 'application/json'
      }
    }).then(res => res.json())
      .then(response => {
        if (_.isNil(response.link) || _.isNil(response.id)) {
          return Promise.reject()
        }
        firstNote.blog = {
          blogLink: response.link,
          blogId: response.id
        }
        this.save(firstNote)
        this.confirmPublish(firstNote)
      })
      .catch((error) => {
        console.error(error)
        this.confirmPublishError()
      })
  }
Example #2
0
            tagNoteSet = new Set(tagNoteSet)
            tagNoteSet.delete(noteKey)
          })
        })
      }
      return state
    case 'RENAME_STORAGE':
      state = Object.assign({}, state)
      state.storageMap = new Map(state.storageMap)
      state.storageMap.set(action.storage.key, action.storage)
      return state
  }
  return state
}

const defaultConfig = ConfigManager.get()

function config (state = defaultConfig, action) {
  switch (action.type) {
    case 'SET_IS_SIDENAV_FOLDED':
      state.isSideNavFolded = action.isFolded
      return Object.assign({}, state)
    case 'SET_ZOOM':
      state.zoom = action.zoom
      return Object.assign({}, state)
    case 'SET_LIST_WIDTH':
      state.listWidth = action.listWidth
      return Object.assign({}, state)
    case 'SET_NAV_WIDTH':
      state.navWidth = action.navWidth
      return Object.assign({}, state)
Example #3
0
  constructor (options = {}) {
    const config = ConfigManager.get()
    const defaultOptions = {
      typographer: config.preview.smartQuotes,
      linkify: true,
      html: true,
      xhtmlOut: true,
      breaks: config.preview.breaks,
      highlight: function (str, lang) {
        const delimiter = ':'
        const langInfo = lang.split(delimiter)
        const langType = langInfo[0]
        const fileName = langInfo[1] || ''
        const firstLineNumber = parseInt(langInfo[2], 10)

        if (langType === 'flowchart') {
          return `<pre class="flowchart">${str}</pre>`
        }
        if (langType === 'sequence') {
          return `<pre class="sequence">${str}</pre>`
        }
        if (langType === 'chart') {
          return `<pre class="chart">${str}</pre>`
        }
        if (langType === 'mermaid') {
          return `<pre class="mermaid">${str}</pre>`
        }
        return '<pre class="code CodeMirror">' +
          '<span class="filename">' + fileName + '</span>' +
          createGutter(str, firstLineNumber) +
          '<code class="' + langType + '">' +
          str +
          '</code></pre>'
      },
      sanitize: 'STRICT'
    }

    const updatedOptions = Object.assign(defaultOptions, options)
    this.md = markdownit(updatedOptions)

    if (updatedOptions.sanitize !== 'NONE') {
      const allowedTags = ['iframe', 'input', 'b',
        'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7', 'h8', 'br', 'b', 'i', 'strong', 'em', 'a', 'pre', 'code', 'img', 'tt',
        'div', 'ins', 'del', 'sup', 'sub', 'p', 'ol', 'ul', 'table', 'thead', 'tbody', 'tfoot', 'blockquote',
        'dl', 'dt', 'dd', 'kbd', 'q', 'samp', 'var', 'hr', 'ruby', 'rt', 'rp', 'li', 'tr', 'td', 'th', 's', 'strike', 'summary', 'details'
      ]
      const allowedAttributes = [
        'abbr', 'accept', 'accept-charset',
        'accesskey', 'action', 'align', 'alt', 'axis',
        'border', 'cellpadding', 'cellspacing', 'char',
        'charoff', 'charset', 'checked',
        'clear', 'cols', 'colspan', 'color',
        'compact', 'coords', 'datetime', 'dir',
        'disabled', 'enctype', 'for', 'frame',
        'headers', 'height', 'hreflang',
        'hspace', 'ismap', 'label', 'lang',
        'maxlength', 'media', 'method',
        'multiple', 'name', 'nohref', 'noshade',
        'nowrap', 'open', 'prompt', 'readonly', 'rel', 'rev',
        'rows', 'rowspan', 'rules', 'scope',
        'selected', 'shape', 'size', 'span',
        'start', 'summary', 'tabindex', 'target',
        'title', 'type', 'usemap', 'valign', 'value',
        'vspace', 'width', 'itemprop'
      ]

      if (updatedOptions.sanitize === 'ALLOW_STYLES') {
        allowedTags.push('style')
        allowedAttributes.push('style')
      }

      // Sanitize use rinput before other plugins
      this.md.use(sanitize, {
        allowedTags,
        allowedAttributes: {
          '*': allowedAttributes,
          'a': ['href'],
          'div': ['itemscope', 'itemtype'],
          'blockquote': ['cite'],
          'del': ['cite'],
          'ins': ['cite'],
          'q': ['cite'],
          'img': ['src', 'width', 'height'],
          'iframe': ['src', 'width', 'height', 'frameborder', 'allowfullscreen'],
          'input': ['type', 'id', 'checked']
        },
        allowedIframeHostnames: ['www.youtube.com']
      })
    }

    this.md.use(emoji, {
      shortcuts: {}
    })
    this.md.use(math, {
      inlineOpen: config.preview.latexInlineOpen,
      inlineClose: config.preview.latexInlineClose,
      blockOpen: config.preview.latexBlockOpen,
      blockClose: config.preview.latexBlockClose,
      inlineRenderer: function (str) {
        let output = ''
        try {
          output = katex.renderToString(str.trim())
        } catch (err) {
          output = `<span class="katex-error">${err.message}</span>`
        }
        return output
      },
      blockRenderer: function (str) {
        let output = ''
        try {
          output = katex.renderToString(str.trim(), { displayMode: true })
        } catch (err) {
          output = `<div class="katex-error">${err.message}</div>`
        }
        return output
      }
    })
    this.md.use(require('markdown-it-imsize'))
    this.md.use(require('markdown-it-footnote'))
    this.md.use(require('markdown-it-multimd-table'))
    this.md.use(require('markdown-it-named-headers'), {
      slugify: (header) => {
        return encodeURI(header.trim()
          .replace(/[\]\[\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~]/g, '')
          .replace(/\s+/g, '-'))
          .replace(/\-+$/, '')
      }
    })
    this.md.use(require('markdown-it-kbd'))
    this.md.use(require('markdown-it-admonition'))

    const deflate = require('markdown-it-plantuml/lib/deflate')
    this.md.use(require('markdown-it-plantuml'), '', {
      generateSource: function (umlCode) {
        const stripTrailingSlash = (url) => url.endsWith('/') ? url.slice(0, -1) : url
        const serverAddress = stripTrailingSlash(config.preview.plantUMLServerAddress) + '/svg'
        const s = unescape(encodeURIComponent(umlCode))
        const zippedCode = deflate.encode64(
          deflate.zip_deflate(`@startuml\n${s}\n@enduml`, 9)
        )
        return `${serverAddress}/${zippedCode}`
      }
    })

    // Override task item
    this.md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
      let content, terminate, i, l, token
      let nextLine = startLine + 1
      const terminatorRules = state.md.block.ruler.getRules('paragraph')
      const endLine = state.lineMax

      // jump line-by-line until empty one or EOF
      for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
        // this would be a code block normally, but after paragraph
        // it's considered a lazy continuation regardless of what's there
        if (state.sCount[nextLine] - state.blkIndent > 3) { continue }

        // quirk for blockquotes, this line should already be checked by that rule
        if (state.sCount[nextLine] < 0) { continue }

        // Some tags can terminate paragraph without empty line.
        terminate = false
        for (i = 0, l = terminatorRules.length; i < l; i++) {
          if (terminatorRules[i](state, nextLine, endLine, true)) {
            terminate = true
            break
          }
        }
        if (terminate) { break }
      }

      content = state.getLines(startLine, nextLine, state.blkIndent, false).trim()

      state.line = nextLine

      token = state.push('paragraph_open', 'p', 1)
      token.map = [startLine, state.line]

      if (state.parentType === 'list') {
        const match = content.match(/^\[( |x)\] ?(.+)/i)
        if (match) {
          const liToken = lastFindInArray(state.tokens, token => token.type === 'list_item_open')
          if (liToken) {
            if (!liToken.attrs) {
              liToken.attrs = []
            }
            liToken.attrs.push(['class', 'taskListItem'])
          }
          content = `<label class='taskListItem${match[1] !== ' ' ? ' checked' : ''}' for='checkbox-${startLine + 1}'><input type='checkbox'${match[1] !== ' ' ? ' checked' : ''} id='checkbox-${startLine + 1}'/> ${content.substring(4, content.length)}</label>`
        }
      }

      token = state.push('inline', '', 0)
      token.content = content
      token.map = [startLine, state.line]
      token.children = []

      token = state.push('paragraph_close', 'p', -1)

      return true
    })

    if (config.preview.smartArrows) {
      this.md.use(smartArrows)
    }

    // Add line number attribute for scrolling
    const originalRender = this.md.renderer.render
    this.md.renderer.render = (tokens, options, env) => {
      tokens.forEach((token) => {
        switch (token.type) {
          case 'heading_open':
          case 'paragraph_open':
          case 'blockquote_open':
          case 'table_open':
            token.attrPush(['data-line', token.map[0]])
        }
      })
      const result = originalRender.call(this.md.renderer, tokens, options, env)
      return result
    }
    // FIXME We should not depend on global variable.
    window.md = this.md
  }