surveybuilder
Version:
Build surveys in the most advanced, flexable, and more moderated way, for all users to build and see.
804 lines (611 loc) • 22.6 kB
text/coffeescript
KeyCodes =
tab: 9
enter: 13
ctrl: 17
space: 32
b: 66
i: 73
u: 85
q: 81
class MarkdownEditor
listFormat = /^(\s*(-|\*|\+|\d+?\.)\s+(\[(\s|x)\]\s+)?)(\S*)/
hrFormat = /^\s{0,3}\s*((-\s+-\s+-(\s+-)*)|(\*\s+\*\s+\*(\s+\*)*))\s*$/
rowFormat = /^\s{0,3}\|(.*?\|)+\s*$/
rowSepFormat = /^\s{0,3}\|(\s*:?---+:?\s*\|)+\s*$/
emptyRowFormat = /^\s{0,3}\|(\s*?\|)+\s*$/
beginCodeblockFormat = /^\s{0,3}((```+)|(~~~+))(\S*\s*)$/
endCodeblockFormat = /^\s{0,3}((```+)|(~~~+))$/
makingTableFormat = /^(:?)(\d+)x(\d+)(:?)$/
numberFormat = /^-?\d+[\d\.]*$/
functionFormat = /^=\s*(\S+)\s*$/
tableFunctions = ['sum', 'average', 'max', 'min', 'count']
constructor: (, ) ->
@$el = $()
= = 0
= ''
+= ' ' for i in [0....tabSize]
@$el.on 'keydown.markdownEditor', (e) =>
if e.keyCode == KeyCodes.enter && !e.shiftKey
if .list
if .table
if .codeblock
if e.keyCode == KeyCodes.space && e.shiftKey && !e.ctrlKey && !e.metaKey
text =
currentLine =
if .list
if .autoTable
if .csvToTable
if .sortTable
if .tableFunction
if e.keyCode == KeyCodes.tab
if e.ctrlKey && !e.metaKey && !e.shiftKey && e.which != KeyCodes.ctrl
getTextArray: ->
.split('')
getText: ->
.value
supportInputListFormat: (e) ->
text =
currentLine =
return if currentLine.match(hrFormat)
match = currentLine.match(listFormat)
return unless match
pos =
return if text[pos] && text[pos] != "\n"
if match[5].length <= 0
return
extSpace = if e.ctrlKey then else ''
e.preventDefault()
.onInsertedList?(e)
toggleCheck: (e, text, currentLine) ->
matches = currentLine.match(listFormat)
return unless matches
return unless matches[4]
line = ''
if matches[4] == 'x'
line = currentLine.replace('[x]', '[ ]')
else
line = currentLine.replace('[ ]', '[x]')
pos =
e.preventDefault()
replaceCurrentLine: (text, pos, oldLine, newLine) ->
beginPos =
text.splice(beginPos, oldLine.length, newLine)
.value = text.join('')
supportInputTableFormat: (e) ->
text =
currentLine =
selectionStart =
match = currentLine.match(rowFormat)
return unless match
return if
return if selectionStart ==
if currentLine.match(emptyRowFormat) &&
return
e.preventDefault()
rows = -1
for char in currentLine
rows++ if char == '|'
prevPos =
sep = ''
unless
sep = "\n|"
for i in [0...rows]
sep += " #{@options.tableSeparator} |"
row = "\n|"
for i in [0...rows]
row += ' |'
text =
pos = prevPos + sep.length + row.length - rows * 3 + 1
.onInsertedTable?(e)
supportCodeblockFormat: (e) ->
text =
selectionStart =
currentLine =
match = currentLine.match(beginCodeblockFormat)
return if text[selectionStart + 1] && text[selectionStart + 1] != "\n"
return unless match
return unless
e.preventDefault()
.onInsertedCodeblock?(e)
requireCodeblockEnd: (text, selectionStart) ->
innerCodeblock =
return false if innerCodeblock
pos =
while pos <= text.length
line =
if innerCodeblock && line.match(endCodeblockFormat)
return false
else if !innerCodeblock && line.match(beginCodeblockFormat)
innerCodeblock = true
pos += line.length + 1
true
isInnerCodeblock: (text, selectionStart = ) ->
innerCodeblock = false
pos = 0
endPos = - 1
while pos < endPos
line =
if innerCodeblock && line.match(endCodeblockFormat)
innerCodeblock = false
else if !innerCodeblock && line.match(beginCodeblockFormat)
innerCodeblock = true
pos += line.length + 1
innerCodeblock
makeTable: (e, text, currentLine) ->
return if
matches = currentLine.match(makingTableFormat)
return unless matches
e.preventDefault()
alignLeft = !!matches[1].length
alignRight = !!matches[4].length
table =
pos =
.onMadeTable?(e)
buildTable: (rowsCount, colsCount, options = {}) ->
separator = "---"
separator = ":#{separator}" if options.alignLeft
separator = "#{separator}:" if options.alignRight
table = "|"
for i in [0...rowsCount]
table += ' |'
table += "\n|"
for i in [0...rowsCount]
table += " #{separator} |"
for i in [0...(colsCount - 1)]
table += "\n|"
for j in [0...rowsCount]
table += " |"
table
csvToTable: (e, text, currentLine) ->
selectedText =
lines = selectedText.split("\n")
return if lines.length <= 1
startPos = null
endPos =
csvLines = []
for line in lines
rows = line.split(',')
if rows.length > 1
csvLines.push(rows)
startPos ?= endPos
else if csvLines.length > 0
break
endPos += line.length + 1
return if csvLines <= 1
e.preventDefault()
table = ''
for line, i in csvLines
table += "|"
for cell in line
table += " #{@trim(cell)} |"
table += "\n"
if i == 0
table += "|"
for j in [0...line.length]
table += " #{@options.tableSeparator} |"
table += "\n"
text.splice(startPos, endPos - startPos, table)
.value = text.join('')
.onMadeTable?(e)
tableFunction: (e, text, currentLine) ->
return if
col = - 1
row =
return if col < 0
return unless row?
e.preventDefault()
data =
currentCellText = data.lines[row].values[col]
return if typeof currentCellText != 'string'
match = currentCellText.match(functionFormat)
return unless match
inputFunction = match[1]
inCaseSensitiveFunction = new RegExp("^#{inputFunction}$", 'i')
for tableFunction in tableFunctions
if tableFunction.match(inCaseSensitiveFunction)
result = @["#{tableFunction}TableFunction"](data, col, row)
if result?
return
countTableFunction: (data, col, row) ->
data.lines.length - 1
maxTableFunction: (data, col, row) ->
max = -Infinity
for line in data.lines
if typeof line.values[col] == 'number' && max < line.values[col]
max = line.values[col]
else
number = parseFloat(line.values[col])
max = number if number? && !isNaN(number) && max < number
return null if max == -Infinity
max
round: (num) ->
w = Math.pow(10, .significantFigures)
Math.round(num * w) / w
minTableFunction: (data, col, row) ->
min = Infinity
for line in data.lines
if typeof line.values[col] == 'number' && min > line.values[col]
min = line.values[col]
else
number = parseFloat(line.values[col])
min = number if number? && !isNaN(number) && min > number
return null if min == Infinity
min
averageTableFunction: (data, col, row) ->
sumTableFunction: (data, col, row) ->
sum = 0.0
for line in data.lines
if typeof line.values[col] == 'number'
sum += line.values[col]
else
number = parseFloat(line.values[col])
sum += number if number? && !isNaN(number)
sum
replaceCurrentCol: (text, str, pos = ) ->
sp = pos
ep = pos
while sp > 0 && text[sp-1] != '|'
sp--
while text[ep] && text[ep] != '|'
ep++
text.splice(sp, ep - sp, " #{str} ")
.value = text.join('')
sortTable: (e, text, currentLine) ->
return if || !
e.preventDefault()
prevPos =
col = - 1
data =
asc = false
for i in [1...data.lines.length]
if 0 <
asc = true
break
data.lines.sort (a, b) =>
body = ''
for line in data.lines
body += "#{line.text}\n"
text.splice(data.bodyStart, body.length, body)
.value = text.join('')
.onSortedTable?(e)
compare: (a, b, asc = true) ->
x = if asc then 1 else -1
return -1 * x if
return 1 * x if
return 0 if a == b
return (if a < b then -1 else 1) * x
getCurrentCol: (text, currentLine) ->
row =
pos = -
count = 0
for i in [0...Math.min(pos, row.length)]
count++ if row[i] == '|'
count
getCurrentRow: (text, pos = ) ->
pos = - 1
row = 0
line =
while .match(rowFormat)
pos -= line.length + 1
line =
row++
return null if row < 3
row - 3
isEmpty: (v) ->
v == null || v == undefined || v == ''
getTableStart: (text, pos = ) ->
pos = - 1
line =
while .match(rowFormat)
pos -= line.length + 1
line =
pos + 2
isTableLine: (text) ->
text.match(rowFormat)
getCurrentTableData: (text, pos = ) ->
pos =
newLineLeft = 2
while newLineLeft > 0 && text[pos]?
newLineLeft-- if text[pos] == "\n"
pos++
data =
bodyStart: pos
lines: []
while text[pos]? &&
line =
break if line.length <= 0
values = .split('|')
for v,i in values
values[i] =
values[i] = +values[i] if values[i].match?(numberFormat)
data.lines.push
text: line
values: values
pos += line.length + 1
data.bodyEnd = pos
data
trim: (str) ->
str.replace(/^\s+/, '').replace(/\s+$/, '')
isSelectRange: ->
!=
getSelectedText: ->
.slice(, )
setSelectionRange: (, ) ->
.setSelectionRange(, )
replaceEscapedPipe: (text) ->
text.replace(/\\\|/g, '..')
isTableHeader: (text = , pos = ) ->
pos =
line =
!!line.match(rowSepFormat)
isTableBody: (textArray = , pos = - 1) ->
line =
while line.match(rowFormat) && pos > 0
return true if line.match(rowSepFormat)
pos = - 2
line =
false
getPrevLine: (textArray, pos = - 1) ->
pos =
getPosEndOfLine: (textArray, pos = ) ->
pos++ while textArray[pos] && textArray[pos] != "\n"
pos
getPosBeginningOfLine: (textArray, pos = ) ->
pos-- while textArray[pos-1] && textArray[pos-1] != "\n"
pos
getPosBeginningOfLines: (text, startPos = , endPos = ) ->
beginningPositions = []
startPos = + 1
if startPos < endPos
for pos in [startPos..endPos]
break unless text[pos]
beginningPositions.push(pos) if pos > 0 && text[pos-1] == "\n"
beginningPositions
getCurrentLine: (text = , initPos = - 1) ->
pos = initPos
beforeChars = ''
while text[pos] && text[pos] != "\n"
beforeChars = "#{text[pos]}#{beforeChars}"
pos--
pos = initPos + 1
afterChars = ''
while text[pos] && text[pos] != "\n"
afterChars = "#{afterChars}#{text[pos]}"
pos++
"#{beforeChars}#{afterChars}"
removeCurrentLine: (textArray) ->
endPos =
beginPos =
removeLength = endPos - beginPos
textArray.splice(beginPos, removeLength)
.value = textArray.join('')
onPressTab: (e) =>
e.preventDefault()
return if .table &&
if .tabToSpace
withCtrl: (e) ->
return unless .fontDecorate
preventDefault = switch e.which
when KeyCodes.b
when KeyCodes.i
when KeyCodes.u
when KeyCodes.q
e.preventDefault() if preventDefault?
wrap: (wrapper) ->
selectionStart =
selectionEnd =
return if selectionStart == selectionEnd
text =
beginningOfLines =
return false if beginningOfLines.length > 1
text.splice(selectionEnd, 0, wrapper)
text.splice(selectionStart, 0, wrapper)
.value = text.join('')
true
moveCursorOnTableCell: (e) ->
text =
currentLine =
return false unless currentLine.match(rowFormat)
if e.shiftKey
else
true
tabToSpace: (e) ->
text =
currentPos =
beginningOfLines =
if beginningOfLines.length <= 1
currentLine =
if .list && currentLine.match(listFormat) && !currentLine.match(hrFormat)
else if !e.shiftKey
else
insertSpacesToBeginningOfLines: (text, currentPos, beginningOfLines, isBack) ->
listPositions = []
dPos = 0
for pos in beginningOfLines
pos += dPos
currentLine =
listPositions.push(pos)
if isBack
if currentLine.indexOf() == 0
text.splice(pos, .tabSize)
dPos -= .tabSize
else
for i in [0....tabSize]
text.splice(pos, 0, ' ')
dPos += .tabSize
.value = text.join('')
if listPositions.length > 1
else
if dPos < 0
beginPos =
for i in [-1..-.tabSize]
if (!text[currentPos+i] || text[currentPos+i] == "\n") && listPositions[0] > beginPos
currentPos = listPositions[0] - dPos
break
moveToPrevCell: (text, pos = - 1) ->
overSep = false
prevLine = false
ep = pos
while text[ep]
return false if overSep && ep < 0 || !overSep && ep <= 0
return false if prevLine && text[ep] != ' ' && text[ep] != '|'
if !overSep
if text[ep] == '|'
overSep = true
prevLine = false
else if text[ep] != ' '
if text[ep] == "\n"
overSep = false
prevLine = true
else
ep++ if text[ep] == '|'
ep++ if text[ep] == ' '
break
ep--
return false if ep < 0
ssp = sp = ep
epAdded = false
while text[sp] && text[sp] != '|'
if text[sp] != ' '
ssp = sp
unless epAdded
ep++
epAdded = true
sp--
true
moveToNextCell: (text, pos = ) ->
overSep = false
overSepSpace = false
eep = null
sp = pos
while text[sp]
if sp > 0 && text[sp-1] == "\n" && text[sp] != '|'
sp--
eep = sp
break
if !overSep
if text[sp] == '|'
overSep = true
else if text[sp] != ' '
if text[sp] == "\n"
overSep = false
else
break
else
break if overSepSpace
overSepSpace = true
sp++
unless text[sp]
sp--
eep = sp
unless eep
eep = ep = sp
while text[ep] && text[ep] != '|'
eep = ep + 1 if text[ep] != ' '
ep++
true
insertSpaces: (text, pos) ->
nextPos = + .length
insert: (textArray, insertText, pos = ) ->
textArray.splice(pos, 0, insertText)
.value = textArray.join('')
pos += insertText.length
getSelectionStart: ->
.selectionStart
getSelectionEnd: ->
.selectionEnd
destroy: ->
@$el.off('keydown.markdownEditor').data('markdownEditor', null)
@$el = null
startUpload: (name) ->
text =
pos =
insertText =
insertText = "\n#{insertText}" if pos > 0 && text[pos-1] != "\n"
insertText = "#{insertText}\n" if pos < text.length - 1 && text[pos] != "\n"
cancelUpload: (name) ->
.value = .replace(, '')
buildUploadingText: (name) ->
.uploadingFormat(name)
finishUpload: (name, options = {}) ->
text =
finishedUploadText = options.text || ''
if finishedUploadText.length <= 0 && options.url || options.alt
finishedUploadText = ""
finishedUploadText = "[#{finishedUploadText}](#{options.href})" if options.href?
uploadingText =
uploadingTextPos = text.indexOf(uploadingText)
if uploadingTextPos >= 0
selectionStart =
selectionEnd =
.value = text.replace(uploadingText, finishedUploadText)
pos = selectionStart + (finishedUploadText.length - uploadingText.length)
else
$.fn.markdownEditor = (options = {}) ->
if typeof options == 'string'
args = Array.prototype.slice.call(arguments).slice(1)
markdownEditor =
return markdownEditor[options]?.apply(markdownEditor, args)
else
options = $.extend
tabSize: 4
onInsertedList: null
onInsertedTable: null
onInsertedCodeblock: null
onSortedTable: null
onMadeTable: null
tabToSpace: true
list: true
table: true
fontDecorate: true
codeblock: true
autoTable: true
tableSeparator: '---'
csvToTable: true
sortTable: true
tableFunction: true
significantFigures: 4
uploadingFormat: (name) ->
"![Uploading... #{name}]()"
, options
->
$(@).data('markdownEditor', new MarkdownEditor(@, options))
@