Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
coopwire-hypothesis
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
孙灵跃 Leon Sun
coopwire-hypothesis
Commits
2dc90311
Commit
2dc90311
authored
Feb 25, 2016
by
Nick Stenning
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #2979 from hypothesis/editor-js-conversion-and-tests
Convert <markdown> component to JS and add basic tests
parents
b32ef2c0
2f64665b
Changes
6
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
599 additions
and
375 deletions
+599
-375
.jshintrc
.jshintrc
+0
-4
markdown.coffee
h/static/scripts/directive/markdown.coffee
+0
-370
markdown.js
h/static/scripts/directive/markdown.js
+491
-0
markdown-test.js
h/static/scripts/directive/test/markdown-test.js
+105
-0
karma.config.js
h/static/scripts/karma.config.js
+1
-0
bootstrap.js
h/static/scripts/test/bootstrap.js
+2
-1
No files found.
.jshintrc
View file @
2dc90311
...
...
@@ -13,10 +13,7 @@
"chrome": false,
"h": false,
"Promise": false,
"angular": false,
"chai": false,
"moment": false,
"jstz": false,
"sinon": false,
"JSON": false
},
...
...
@@ -30,7 +27,6 @@
"URL",
"after",
"afterEach",
"angular",
"assert",
"before",
"beforeEach",
...
...
h/static/scripts/directive/markdown.coffee
deleted
100644 → 0
View file @
b32ef2c0
katex
=
require
(
'katex'
)
mediaEmbedder
=
require
(
'../media-embedder'
)
angular
=
require
(
'angular'
)
loadMathJax
=
->
if
!
MathJax
?
$
.
ajax
{
url
:
"https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML-full"
dataType
:
'script'
cache
:
true
complete
:
->
# MathJax configuration overides.
MathJax
.
Hub
.
Config
({
showMathMenu
:
false
displayAlign
:
"left"
})
}
###*
# @ngdoc directive
# @name markdown
# @restrict A
# @description
# This directive controls both the rendering and display of markdown, as well as
# the markdown editor.
###
module
.
exports
=
[
'$filter'
,
'$sanitize'
,
'$sce'
,
'$timeout'
,
(
$filter
,
$sanitize
,
$sce
,
$timeout
)
->
link
:
(
scope
,
elem
,
attr
,
ctrl
)
->
return
unless
ctrl
?
input
=
elem
[
0
].
querySelector
(
'.js-markdown-input'
)
inputEl
=
angular
.
element
(
input
)
output
=
elem
[
0
].
querySelector
(
'.js-markdown-preview'
)
userSelection
=
->
if
input
.
selectionStart
!=
undefined
startPos
=
input
.
selectionStart
endPos
=
input
.
selectionEnd
selectedText
=
input
.
value
.
substring
(
startPos
,
endPos
)
textBefore
=
input
.
value
.
substring
(
0
,
(
startPos
))
textAfter
=
input
.
value
.
substring
(
endPos
)
selection
=
{
before
:
textBefore
after
:
textAfter
selection
:
selectedText
start
:
startPos
end
:
endPos
}
return
selection
insertMarkup
=
(
value
,
selectionStart
,
selectionEnd
)
->
# New value is set for the input
input
.
value
=
value
# A new selection is set, or the cursor is positioned inside the input.
input
.
selectionStart
=
selectionStart
input
.
selectionEnd
=
selectionEnd
# Focus the input
input
.
focus
()
applyInlineMarkup
=
(
markupL
,
innertext
,
markupR
)
->
markupR
or=
markupL
text
=
userSelection
()
if
text
.
selection
==
""
newtext
=
text
.
before
+
markupL
+
innertext
+
markupR
+
text
.
after
start
=
(
text
.
before
+
markupL
).
length
end
=
(
text
.
before
+
innertext
+
markupR
).
length
insertMarkup
(
newtext
,
start
,
end
)
else
# Check to see if markup has already been applied before to the selection.
slice1
=
text
.
before
.
slice
(
text
.
before
.
length
-
markupL
.
length
)
slice2
=
text
.
after
.
slice
(
0
,
markupR
.
length
)
if
(
slice1
==
markupL
and
slice2
==
markupR
)
# Remove markup
newtext
=
(
text
.
before
.
slice
(
0
,
(
text
.
before
.
length
-
markupL
.
length
))
+
text
.
selection
+
text
.
after
.
slice
(
markupR
.
length
)
)
start
=
text
.
before
.
length
-
markupL
.
length
end
=
(
text
.
before
+
text
.
selection
).
length
-
markupR
.
length
insertMarkup
(
newtext
,
start
,
end
)
else
# Apply markup
newtext
=
text
.
before
+
markupL
+
text
.
selection
+
markupR
+
text
.
after
start
=
(
text
.
before
+
markupL
).
length
end
=
(
text
.
before
+
text
.
selection
+
markupR
).
length
insertMarkup
(
newtext
,
start
,
end
)
scope
.
insertBold
=
->
applyInlineMarkup
(
"**"
,
"Bold"
)
scope
.
insertItalic
=
->
applyInlineMarkup
(
"*"
,
"Italic"
)
scope
.
insertMath
=
->
text
=
userSelection
()
index
=
text
.
before
.
length
if
(
index
==
0
or
input
.
value
[
index
-
1
]
==
'
\n
'
or
(
input
.
value
[
index
-
1
]
==
'$'
and
input
.
value
[
index
-
2
]
==
'$'
)
)
applyInlineMarkup
(
'$$'
,
'Insert LaTeX'
)
else
applyInlineMarkup
(
'
\\
('
,
'Insert LaTeX'
,
'
\\
)'
)
scope
.
insertLink
=
->
text
=
userSelection
()
if
text
.
selection
==
""
newtext
=
text
.
before
+
"[Link Text](https://example.com)"
+
text
.
after
start
=
text
.
before
.
length
+
1
end
=
text
.
before
.
length
+
10
insertMarkup
(
newtext
,
start
,
end
)
else
# Check to see if markup has already been applied to avoid double presses.
if
text
.
selection
==
"Link Text"
or
text
.
selection
==
"https://example.com"
return
newtext
=
text
.
before
+
'['
+
text
.
selection
+
'](https://example.com)'
+
text
.
after
start
=
(
text
.
before
+
text
.
selection
).
length
+
3
end
=
(
text
.
before
+
text
.
selection
).
length
+
22
insertMarkup
(
newtext
,
start
,
end
)
scope
.
insertIMG
=
->
text
=
userSelection
()
if
text
.
selection
==
""
newtext
=
text
.
before
+
"![Image Description](https://yourimage.jpg)"
+
text
.
after
start
=
text
.
before
.
length
+
21
end
=
text
.
before
.
length
+
42
insertMarkup
(
newtext
,
start
,
end
)
else
# Check to see if markup has already been applied to avoid double presses.
if
text
.
selection
==
"https://yourimage.jpg"
return
newtext
=
text
.
before
+
'!['
+
text
.
selection
+
'](https://yourimage.jpg)'
+
text
.
after
start
=
(
text
.
before
+
text
.
selection
).
length
+
4
end
=
(
text
.
before
+
text
.
selection
).
length
+
25
insertMarkup
(
newtext
,
start
,
end
)
scope
.
applyBlockMarkup
=
(
markup
)
->
text
=
userSelection
()
if
text
.
selection
!=
""
newstring
=
""
index
=
text
.
before
.
length
if
index
==
0
# The selection takes place at the very start of the input
for
char
in
text
.
selection
if
char
==
"
\n
"
newstring
=
newstring
+
"
\n
"
+
markup
else
if
index
==
0
newstring
=
newstring
+
markup
+
char
else
newstring
=
newstring
+
char
index
+=
1
else
newlinedetected
=
false
if
input
.
value
.
substring
(
index
-
1
).
charAt
(
0
)
==
"
\n
"
# Look to see if the selection falls at the beginning of a new line.
newstring
=
newstring
+
markup
newlinedetected
=
true
for
char
in
text
.
selection
if
char
==
"
\n
"
newstring
=
newstring
+
"
\n
"
+
markup
newlinedetected
=
true
else
newstring
=
newstring
+
char
index
+=
1
if
not
newlinedetected
# Edge case: The selection does not include any new lines and does not start at 0.
# We need to find the newline before the currently selected text and add markup there.
i
=
0
indexoflastnewline
=
undefined
newstring
=
""
for
char
in
(
text
.
before
+
text
.
selection
)
if
char
==
"
\n
"
indexoflastnewline
=
i
newstring
=
newstring
+
char
i
++
if
indexoflastnewline
==
undefined
# The partial selection happens to fall on the firstline
newstring
=
markup
+
newstring
else
newstring
=
(
newstring
.
substring
(
0
,
(
indexoflastnewline
+
1
))
+
markup
+
newstring
.
substring
(
indexoflastnewline
+
1
)
)
value
=
newstring
+
text
.
after
start
=
(
text
.
before
+
markup
).
length
end
=
(
text
.
before
+
text
.
selection
+
markup
).
length
insertMarkup
(
value
,
start
,
end
)
return
# Sets input value and selection for cases where there are new lines in the selection
# or the selection is at the start
value
=
text
.
before
+
newstring
+
text
.
after
start
=
(
text
.
before
+
newstring
).
length
end
=
(
text
.
before
+
newstring
).
length
insertMarkup
(
value
,
start
,
end
)
else
if
input
.
value
.
substring
((
text
.
start
-
1
),
text
.
start
)
==
"
\n
"
# Edge case, no selection, the cursor is on a new line.
value
=
text
.
before
+
markup
+
text
.
selection
+
text
.
after
start
=
(
text
.
before
+
markup
).
length
end
=
(
text
.
before
+
markup
).
length
insertMarkup
(
value
,
start
,
end
)
else
# No selection, cursor is not on new line.
# Check to see if markup has already been inserted.
if
text
.
before
.
slice
(
text
.
before
.
length
-
markup
.
length
)
==
markup
newtext
=
(
text
.
before
.
substring
(
0
,
(
index
))
+
"
\n
"
+
text
.
before
.
substring
(
index
+
1
+
markup
.
length
)
+
text
.
after
)
i
=
0
for
char
in
text
.
before
if
char
==
"
\n
"
and
i
!=
0
index
=
i
i
+=
1
if
!
index
# If the line of text happens to fall on the first line and index is not set.
# Check to see if markup has already been inserted and undo it.
if
text
.
before
.
slice
(
0
,
markup
.
length
)
==
markup
newtext
=
text
.
before
.
substring
(
markup
.
length
)
+
text
.
after
start
=
text
.
before
.
length
-
markup
.
length
end
=
text
.
before
.
length
-
markup
.
length
insertMarkup
(
newtext
,
start
,
end
)
else
newtext
=
markup
+
text
.
before
.
substring
(
0
)
+
text
.
after
start
=
(
text
.
before
+
markup
).
length
end
=
(
text
.
before
+
markup
).
length
insertMarkup
(
newtext
,
start
,
end
)
# Check to see if markup has already been inserted and undo it.
else
if
text
.
before
.
slice
((
index
+
1
),
(
index
+
1
+
markup
.
length
))
==
markup
newtext
=
(
text
.
before
.
substring
(
0
,
(
index
))
+
"
\n
"
+
text
.
before
.
substring
(
index
+
1
+
markup
.
length
)
+
text
.
after
)
start
=
text
.
before
.
length
-
markup
.
length
end
=
text
.
before
.
length
-
markup
.
length
insertMarkup
(
newtext
,
start
,
end
)
else
newtext
=
(
text
.
before
.
substring
(
0
,
(
index
))
+
"
\n
"
+
markup
+
text
.
before
.
substring
(
index
+
1
)
+
text
.
after
)
start
=
(
text
.
before
+
markup
).
length
end
=
(
text
.
before
+
markup
).
length
insertMarkup
(
newtext
,
start
,
end
)
scope
.
insertList
=
->
scope
.
applyBlockMarkup
(
"* "
)
scope
.
insertNumList
=
->
scope
.
applyBlockMarkup
(
"1. "
)
scope
.
insertQuote
=
->
scope
.
applyBlockMarkup
(
"> "
)
# Keyboard shortcuts for bold, italic, and link.
elem
.
on
'keydown'
,
(
e
)
->
shortcuts
=
66
:
scope
.
insertBold
73
:
scope
.
insertItalic
75
:
scope
.
insertLink
shortcut
=
shortcuts
[
e
.
keyCode
]
if
shortcut
&&
(
e
.
ctrlKey
||
e
.
metaKey
)
e
.
preventDefault
()
shortcut
()
scope
.
preview
=
false
scope
.
togglePreview
=
->
if
!
scope
.
readOnly
scope
.
preview
=
!
scope
.
preview
if
scope
.
preview
output
.
style
.
height
=
input
.
style
.
height
ctrl
.
$render
()
else
input
.
style
.
height
=
output
.
style
.
height
$timeout
->
input
.
focus
()
mathJaxFallback
=
false
renderMathAndMarkdown
=
(
textToCheck
)
->
convert
=
$filter
(
'converter'
)
re
=
/\$\$/g
startMath
=
0
endMath
=
0
indexes
=
(
match
.
index
while
match
=
re
.
exec
(
textToCheck
))
indexes
.
push
(
textToCheck
.
length
)
parts
=
for
index
in
indexes
if
startMath
>
endMath
endMath
=
index
+
2
try
# \\displaystyle tells KaTeX to render the math in display style (full sized fonts).
katex
.
renderToString
(
$sanitize
"
\\
displaystyle {"
+
textToCheck
.
substring
(
startMath
,
index
)
+
"}"
)
catch
loadMathJax
()
mathJaxFallback
=
true
$sanitize
textToCheck
.
substring
(
startMath
,
index
)
else
startMath
=
index
+
2
$sanitize
convert
renderInlineMath
textToCheck
.
substring
(
endMath
,
index
)
htmlString
=
parts
.
join
(
''
)
# Transform the HTML string into a DOM element.
domElement
=
document
.
createElement
(
'div'
)
domElement
.
innerHTML
=
htmlString
mediaEmbedder
.
replaceLinksWithEmbeds
(
domElement
)
return
domElement
.
innerHTML
renderInlineMath
=
(
textToCheck
)
->
re
=
/\\?\\\(|\\?\\\)/g
startMath
=
null
endMath
=
null
match
=
undefined
indexes
=
[]
while
match
=
re
.
exec
(
textToCheck
)
indexes
.
push
match
.
index
for
index
in
indexes
if
startMath
==
null
startMath
=
index
+
2
else
endMath
=
index
if
startMath
!=
null
and
endMath
!=
null
try
math
=
katex
.
renderToString
(
textToCheck
.
substring
(
startMath
,
endMath
))
textToCheck
=
(
textToCheck
.
substring
(
0
,
(
startMath
-
2
))
+
math
+
textToCheck
.
substring
(
endMath
+
2
)
)
startMath
=
null
endMath
=
null
return
renderInlineMath
(
textToCheck
)
catch
loadMathJax
()
mathJaxFallback
=
true
$sanitize
textToCheck
.
substring
(
startMath
,
endMath
)
return
textToCheck
# Re-render the markdown when the view needs updating.
ctrl
.
$render
=
->
if
!
scope
.
readOnly
and
!
scope
.
preview
inputEl
.
val
(
ctrl
.
$viewValue
or
''
)
value
=
ctrl
.
$viewValue
or
''
output
.
innerHTML
=
renderMathAndMarkdown
(
value
)
if
mathJaxFallback
$timeout
(
->
MathJax
?
.
Hub
.
Queue
[
'Typeset'
,
MathJax
.
Hub
,
output
]),
0
,
false
# React to the changes to the input
inputEl
.
bind
'blur change keyup'
,
->
$timeout
->
ctrl
.
$setViewValue
inputEl
.
val
()
# Reset height of output div incase it has been changed.
# Re-render when it becomes uneditable.
# Auto-focus the input box when the widget becomes editable.
scope
.
$watch
'readOnly'
,
(
readOnly
)
->
scope
.
preview
=
false
output
.
style
.
height
=
""
ctrl
.
$render
()
unless
readOnly
then
$timeout
->
input
.
focus
()
require
:
'?ngModel'
restrict
:
'E'
scope
:
readOnly
:
'<'
required
:
'@'
templateUrl
:
'markdown.html'
]
h/static/scripts/directive/markdown.js
0 → 100644
View file @
2dc90311
'use strict'
;
/* globals MathJax */
var
angular
=
require
(
'angular'
);
var
katex
=
require
(
'katex'
);
var
mediaEmbedder
=
require
(
'../media-embedder'
);
var
loadMathJax
=
function
()
{
if
(
!
(
typeof
MathJax
!==
"undefined"
&&
MathJax
!==
null
))
{
return
$
.
ajax
({
url
:
"https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML-full"
,
dataType
:
'script'
,
cache
:
true
,
complete
:
function
()
{
// MathJax configuration overides.
return
MathJax
.
Hub
.
Config
({
showMathMenu
:
false
,
displayAlign
:
"left"
});
}
});
}
};
/**
* @ngdoc directive
* @name markdown
* @restrict A
* @description
* This directive controls both the rendering and display of markdown, as well as
* the markdown editor.
*/
// @ngInject
module
.
exports
=
function
(
$filter
,
$sanitize
,
$sce
,
$timeout
)
{
return
{
link
:
function
(
scope
,
elem
,
attr
,
ctrl
)
{
if
(
!
(
typeof
ctrl
!==
"undefined"
&&
ctrl
!==
null
))
{
return
;
}
var
input
=
elem
[
0
].
querySelector
(
'.js-markdown-input'
);
var
inputEl
=
angular
.
element
(
input
);
var
output
=
elem
[
0
].
querySelector
(
'.js-markdown-preview'
);
var
userSelection
=
function
()
{
var
selection
;
if
(
input
.
selectionStart
!==
undefined
)
{
var
startPos
=
input
.
selectionStart
;
var
endPos
=
input
.
selectionEnd
;
var
selectedText
=
input
.
value
.
substring
(
startPos
,
endPos
);
var
textBefore
=
input
.
value
.
substring
(
0
,
(
startPos
));
var
textAfter
=
input
.
value
.
substring
(
endPos
);
selection
=
{
before
:
textBefore
,
after
:
textAfter
,
selection
:
selectedText
,
start
:
startPos
,
end
:
endPos
};
}
return
selection
;
};
var
insertMarkup
=
function
(
value
,
selectionStart
,
selectionEnd
)
{
// New value is set for the input
input
.
value
=
value
;
// A new selection is set, or the cursor is positioned inside the input.
input
.
selectionStart
=
selectionStart
;
input
.
selectionEnd
=
selectionEnd
;
// Focus the input
return
input
.
focus
();
};
var
applyInlineMarkup
=
function
(
markupL
,
innertext
,
markupR
)
{
if
(
!
markupR
)
{
markupR
=
markupL
;
}
var
newtext
;
var
end
;
var
start
;
var
text
=
userSelection
();
if
(
text
.
selection
===
""
)
{
newtext
=
text
.
before
+
markupL
+
innertext
+
markupR
+
text
.
after
;
start
=
(
text
.
before
+
markupL
).
length
;
end
=
(
text
.
before
+
innertext
+
markupR
).
length
;
return
insertMarkup
(
newtext
,
start
,
end
);
}
else
{
// Check to see if markup has already been applied before to the selection.
var
slice1
=
text
.
before
.
slice
(
text
.
before
.
length
-
markupL
.
length
);
var
slice2
=
text
.
after
.
slice
(
0
,
markupR
.
length
);
if
(
slice1
===
markupL
&&
slice2
===
markupR
)
{
// Remove markup
newtext
=
(
text
.
before
.
slice
(
0
,
(
text
.
before
.
length
-
markupL
.
length
))
+
text
.
selection
+
text
.
after
.
slice
(
markupR
.
length
)
);
start
=
text
.
before
.
length
-
markupL
.
length
;
end
=
(
text
.
before
+
text
.
selection
).
length
-
markupR
.
length
;
return
insertMarkup
(
newtext
,
start
,
end
);
}
else
{
// Apply markup
newtext
=
text
.
before
+
markupL
+
text
.
selection
+
markupR
+
text
.
after
;
start
=
(
text
.
before
+
markupL
).
length
;
end
=
(
text
.
before
+
text
.
selection
+
markupR
).
length
;
return
insertMarkup
(
newtext
,
start
,
end
);
}
}
};
scope
.
insertBold
=
function
()
{
return
applyInlineMarkup
(
"**"
,
"Bold"
);
};
scope
.
insertItalic
=
function
()
{
return
applyInlineMarkup
(
"*"
,
"Italic"
);
};
scope
.
insertMath
=
function
()
{
var
text
=
userSelection
();
var
index
=
text
.
before
.
length
;
if
(
index
===
0
||
input
.
value
[
index
-
1
]
===
'
\
n'
||
(
input
.
value
[
index
-
1
]
===
'$'
&&
input
.
value
[
index
-
2
]
===
'$'
)
)
{
return
applyInlineMarkup
(
'$$'
,
'Insert LaTeX'
);
}
else
{
return
applyInlineMarkup
(
'
\\
('
,
'Insert LaTeX'
,
'
\\
)'
);
}
};
scope
.
insertLink
=
function
()
{
var
text
=
userSelection
();
var
newtext
;
var
start
;
var
end
;
if
(
text
.
selection
===
""
)
{
newtext
=
text
.
before
+
"[Link Text](https://example.com)"
+
text
.
after
;
start
=
text
.
before
.
length
+
1
;
end
=
text
.
before
.
length
+
10
;
return
insertMarkup
(
newtext
,
start
,
end
);
}
else
{
// Check to see if markup has already been applied to avoid double presses.
if
(
text
.
selection
===
"Link Text"
||
text
.
selection
===
"https://example.com"
)
{
return
;
}
newtext
=
text
.
before
+
'['
+
text
.
selection
+
'](https://example.com)'
+
text
.
after
;
start
=
(
text
.
before
+
text
.
selection
).
length
+
3
;
end
=
(
text
.
before
+
text
.
selection
).
length
+
22
;
return
insertMarkup
(
newtext
,
start
,
end
);
}
};
scope
.
insertIMG
=
function
()
{
var
text
=
userSelection
();
var
newtext
;
var
start
;
var
end
;
if
(
text
.
selection
===
""
)
{
newtext
=
text
.
before
+
"![Image Description](https://yourimage.jpg)"
+
text
.
after
;
start
=
text
.
before
.
length
+
21
;
end
=
text
.
before
.
length
+
42
;
return
insertMarkup
(
newtext
,
start
,
end
);
}
else
{
// Check to see if markup has already been applied to avoid double presses.
if
(
text
.
selection
===
"https://yourimage.jpg"
)
{
return
;
}
newtext
=
text
.
before
+
'!['
+
text
.
selection
+
'](https://yourimage.jpg)'
+
text
.
after
;
start
=
(
text
.
before
+
text
.
selection
).
length
+
4
;
end
=
(
text
.
before
+
text
.
selection
).
length
+
25
;
return
insertMarkup
(
newtext
,
start
,
end
);
}
};
/* jshint maxcomplexity:false */
scope
.
applyBlockMarkup
=
function
(
markup
)
{
var
text
=
userSelection
();
var
ch
;
var
value
;
var
start
;
var
end
;
var
index
;
var
i
;
var
newtext
;
if
(
text
.
selection
!==
""
)
{
var
newstring
=
""
;
index
=
text
.
before
.
length
;
if
(
index
===
0
)
{
// The selection takes place at the very start of the input
for
(
var
j
=
0
;
j
<
text
.
selection
.
length
;
j
++
)
{
ch
=
text
.
selection
[
j
];
if
(
ch
===
"
\n
"
)
{
newstring
=
newstring
+
"
\n
"
+
markup
;
}
else
if
(
index
===
0
)
{
newstring
=
newstring
+
markup
+
ch
;
}
else
{
newstring
=
newstring
+
ch
;
}
index
+=
1
;
}
}
else
{
var
newlinedetected
=
false
;
if
(
input
.
value
.
substring
(
index
-
1
).
charAt
(
0
)
===
"
\n
"
)
{
// Look to see if the selection falls at the beginning of a new line.
newstring
=
newstring
+
markup
;
newlinedetected
=
true
;
}
for
(
var
k
=
0
;
k
<
text
.
selection
.
length
;
k
++
)
{
ch
=
text
.
selection
[
k
];
if
(
ch
===
"
\n
"
)
{
newstring
=
newstring
+
"
\n
"
+
markup
;
newlinedetected
=
true
;
}
else
{
newstring
=
newstring
+
ch
;
}
index
+=
1
;
}
if
(
!
newlinedetected
)
{
// Edge case: The selection does not include any new lines and does not start at 0.
// We need to find the newline before the currently selected text and add markup there.
i
=
0
;
var
indexoflastnewline
;
newstring
=
""
;
var
iterable
=
text
.
before
+
text
.
selection
;
for
(
var
i1
=
0
;
i1
<
iterable
.
length
;
i1
++
)
{
ch
=
iterable
[
i1
];
if
(
ch
===
"
\n
"
)
{
indexoflastnewline
=
i
;
}
newstring
=
newstring
+
ch
;
i
++
;
}
if
(
indexoflastnewline
===
undefined
)
{
// The partial selection happens to fall on the firstline
newstring
=
markup
+
newstring
;
}
else
{
newstring
=
(
newstring
.
substring
(
0
,
(
indexoflastnewline
+
1
))
+
markup
+
newstring
.
substring
(
indexoflastnewline
+
1
)
);
}
value
=
newstring
+
text
.
after
;
start
=
(
text
.
before
+
markup
).
length
;
end
=
(
text
.
before
+
text
.
selection
+
markup
).
length
;
insertMarkup
(
value
,
start
,
end
);
return
;
}
}
// Sets input value and selection for cases where there are new lines in the selection
// or the selection is at the start
value
=
text
.
before
+
newstring
+
text
.
after
;
start
=
(
text
.
before
+
newstring
).
length
;
end
=
(
text
.
before
+
newstring
).
length
;
return
insertMarkup
(
value
,
start
,
end
);
}
else
if
(
input
.
value
.
substring
((
text
.
start
-
1
),
text
.
start
)
===
"
\n
"
)
{
// Edge case, no selection, the cursor is on a new line.
value
=
text
.
before
+
markup
+
text
.
selection
+
text
.
after
;
start
=
(
text
.
before
+
markup
).
length
;
end
=
(
text
.
before
+
markup
).
length
;
return
insertMarkup
(
value
,
start
,
end
);
}
else
{
// No selection, cursor is not on new line.
// Check to see if markup has already been inserted.
if
(
text
.
before
.
slice
(
text
.
before
.
length
-
markup
.
length
)
===
markup
)
{
newtext
=
(
text
.
before
.
substring
(
0
,
(
index
))
+
"
\n
"
+
text
.
before
.
substring
(
index
+
1
+
markup
.
length
)
+
text
.
after
);
}
i
=
0
;
for
(
var
i2
=
0
,
char
;
i2
<
text
.
before
.
length
;
i2
++
)
{
char
=
text
.
before
[
i2
];
if
(
char
===
"
\n
"
&&
i
!==
0
)
{
index
=
i
;
}
i
+=
1
;
}
if
(
!
index
)
{
// If the line of text happens to fall on the first line and index is not set.
// Check to see if markup has already been inserted and undo it.
if
(
text
.
before
.
slice
(
0
,
markup
.
length
)
===
markup
)
{
newtext
=
text
.
before
.
substring
(
markup
.
length
)
+
text
.
after
;
start
=
text
.
before
.
length
-
markup
.
length
;
end
=
text
.
before
.
length
-
markup
.
length
;
return
insertMarkup
(
newtext
,
start
,
end
);
}
else
{
newtext
=
markup
+
text
.
before
.
substring
(
0
)
+
text
.
after
;
start
=
(
text
.
before
+
markup
).
length
;
end
=
(
text
.
before
+
markup
).
length
;
return
insertMarkup
(
newtext
,
start
,
end
);
}
// Check to see if markup has already been inserted and undo it.
}
else
if
(
text
.
before
.
slice
((
index
+
1
),
(
index
+
1
+
markup
.
length
))
===
markup
)
{
newtext
=
(
text
.
before
.
substring
(
0
,
(
index
))
+
"
\n
"
+
text
.
before
.
substring
(
index
+
1
+
markup
.
length
)
+
text
.
after
);
start
=
text
.
before
.
length
-
markup
.
length
;
end
=
text
.
before
.
length
-
markup
.
length
;
return
insertMarkup
(
newtext
,
start
,
end
);
}
else
{
newtext
=
(
text
.
before
.
substring
(
0
,
(
index
))
+
"
\n
"
+
markup
+
text
.
before
.
substring
(
index
+
1
)
+
text
.
after
);
start
=
(
text
.
before
+
markup
).
length
;
end
=
(
text
.
before
+
markup
).
length
;
return
insertMarkup
(
newtext
,
start
,
end
);
}
}
};
scope
.
insertList
=
function
()
{
return
scope
.
applyBlockMarkup
(
"* "
);
};
scope
.
insertNumList
=
function
()
{
return
scope
.
applyBlockMarkup
(
"1. "
);
};
scope
.
insertQuote
=
function
()
{
return
scope
.
applyBlockMarkup
(
"> "
);
};
// Keyboard shortcuts for bold, italic, and link.
elem
.
on
(
'keydown'
,
function
(
e
)
{
var
shortcuts
=
{
66
:
scope
.
insertBold
,
73
:
scope
.
insertItalic
,
75
:
scope
.
insertLink
};
var
shortcut
=
shortcuts
[
e
.
keyCode
];
if
(
shortcut
&&
(
e
.
ctrlKey
||
e
.
metaKey
))
{
e
.
preventDefault
();
return
shortcut
();
}
});
scope
.
preview
=
false
;
scope
.
togglePreview
=
function
()
{
if
(
!
scope
.
readOnly
)
{
scope
.
preview
=
!
scope
.
preview
;
if
(
scope
.
preview
)
{
output
.
style
.
height
=
input
.
style
.
height
;
return
ctrl
.
$render
();
}
else
{
input
.
style
.
height
=
output
.
style
.
height
;
return
$timeout
(
function
()
{
return
input
.
focus
();
});
}
}
};
var
mathJaxFallback
=
false
;
var
renderInlineMath
=
function
(
textToCheck
)
{
var
re
=
/
\\?\\\(
|
\\?\\\)
/g
;
var
startMath
=
null
;
var
endMath
=
null
;
var
match
;
var
indexes
=
[];
while
((
match
=
re
.
exec
(
textToCheck
)))
{
indexes
.
push
(
match
.
index
);
}
for
(
var
i
=
0
,
index
;
i
<
indexes
.
length
;
i
++
)
{
index
=
indexes
[
i
];
if
(
startMath
===
null
)
{
startMath
=
index
+
2
;
}
else
{
endMath
=
index
;
}
if
(
startMath
!==
null
&&
endMath
!==
null
)
{
try
{
var
math
=
katex
.
renderToString
(
textToCheck
.
substring
(
startMath
,
endMath
));
textToCheck
=
(
textToCheck
.
substring
(
0
,
(
startMath
-
2
))
+
math
+
textToCheck
.
substring
(
endMath
+
2
)
);
startMath
=
null
;
endMath
=
null
;
return
renderInlineMath
(
textToCheck
);
}
catch
(
error
)
{
loadMathJax
();
mathJaxFallback
=
true
;
$sanitize
(
textToCheck
.
substring
(
startMath
,
endMath
));
}
}
}
return
textToCheck
;
};
var
renderMathAndMarkdown
=
function
(
textToCheck
)
{
var
convert
=
$filter
(
'converter'
);
var
re
=
/
\$\$
/g
;
var
startMath
=
0
;
var
endMath
=
0
;
var
indexes
=
(
function
()
{
var
match
;
var
result
=
[];
while
((
match
=
re
.
exec
(
textToCheck
)))
{
result
.
push
(
match
.
index
);
}
return
result
;
})();
indexes
.
push
(
textToCheck
.
length
);
var
parts
=
(
function
()
{
var
result
=
[];
/* jshint -W083 */
for
(
var
i
=
0
,
index
;
i
<
indexes
.
length
;
i
++
)
{
index
=
indexes
[
i
];
result
.
push
((
function
()
{
if
(
startMath
>
endMath
)
{
endMath
=
index
+
2
;
try
{
// \\displaystyle tells KaTeX to render the math in display style (full sized fonts).
return
katex
.
renderToString
(
$sanitize
(
"
\\
displaystyle {"
+
textToCheck
.
substring
(
startMath
,
index
)
+
"}"
));
}
catch
(
error
)
{
loadMathJax
();
mathJaxFallback
=
true
;
return
$sanitize
(
textToCheck
.
substring
(
startMath
,
index
));
}
}
else
{
startMath
=
index
+
2
;
return
$sanitize
(
convert
(
renderInlineMath
(
textToCheck
.
substring
(
endMath
,
index
))));
}
})());
}
/* jshint +W083 */
return
result
;
})();
var
htmlString
=
parts
.
join
(
''
);
// Transform the HTML string into a DOM element.
var
domElement
=
document
.
createElement
(
'div'
);
domElement
.
innerHTML
=
htmlString
;
mediaEmbedder
.
replaceLinksWithEmbeds
(
domElement
);
return
domElement
.
innerHTML
;
};
// Re-render the markdown when the view needs updating.
ctrl
.
$render
=
function
()
{
if
(
!
scope
.
readOnly
&&
!
scope
.
preview
)
{
inputEl
.
val
((
ctrl
.
$viewValue
||
''
));
}
var
value
=
ctrl
.
$viewValue
||
''
;
output
.
innerHTML
=
renderMathAndMarkdown
(
value
);
if
(
mathJaxFallback
)
{
return
$timeout
((
function
()
{
return
((
typeof
MathJax
!==
"undefined"
&&
MathJax
!==
null
)
?
MathJax
.
Hub
:
undefined
).
Queue
([
'Typeset'
,
MathJax
.
Hub
,
output
]);
}),
0
,
false
);
}
};
// React to the changes to the input
inputEl
.
bind
(
'blur change keyup'
,
function
()
{
return
$timeout
(
function
()
{
return
ctrl
.
$setViewValue
(
inputEl
.
val
());
});
});
// Reset height of output div incase it has been changed.
// Re-render when it becomes uneditable.
// Auto-focus the input box when the widget becomes editable.
return
scope
.
$watch
(
'readOnly'
,
function
(
readOnly
)
{
scope
.
preview
=
false
;
output
.
style
.
height
=
""
;
ctrl
.
$render
();
if
(
!
readOnly
)
{
return
$timeout
(
function
()
{
return
input
.
focus
();
});
}
});
},
require
:
'?ngModel'
,
restrict
:
'E'
,
scope
:
{
readOnly
:
'='
,
required
:
'@'
},
templateUrl
:
'markdown.html'
};
};
h/static/scripts/directive/test/markdown-test.js
0 → 100644
View file @
2dc90311
'use strict'
;
var
angular
=
require
(
'angular'
);
var
proxyquire
=
require
(
'proxyquire'
);
var
util
=
require
(
'./util'
);
/**
* Disable calling through to the original module for a stub.
*
* By default proxyquire will call through to the original module
* for any methods not provided by a stub. This function disables
* this behavior for a stub and returns the input stub.
*
* This prevents unintended usage of the original dependency.
*/
function
noCallThru
(
stub
)
{
return
Object
.
assign
(
stub
,
{
'@noCallThru'
:
true
});
}
describe
(
'markdown'
,
function
()
{
function
isHidden
(
element
)
{
return
element
.
classList
.
contains
(
'ng-hide'
);
}
function
inputElement
(
editor
)
{
return
editor
[
0
].
querySelector
(
'.form-input'
);
}
function
viewElement
(
editor
)
{
return
editor
[
0
].
querySelector
(
'.styled-text'
);
}
function
getRenderedHTML
(
editor
)
{
var
contentElement
=
viewElement
(
editor
);
if
(
isHidden
(
contentElement
))
{
return
'rendered markdown is hidden'
;
}
return
contentElement
.
innerHTML
;
}
before
(
function
()
{
angular
.
module
(
'app'
,
[
'ngSanitize'
])
.
directive
(
'markdown'
,
proxyquire
(
'../markdown'
,
{
angular
:
noCallThru
(
angular
),
katex
:
{
renderToString
:
function
(
input
)
{
return
'math:'
+
input
.
replace
(
/$$/g
,
''
);
},
},
'@noCallThru'
:
true
,
}))
.
filter
(
'converter'
,
function
()
{
return
function
(
input
)
{
return
'rendered:'
+
input
;
};
});
});
beforeEach
(
function
()
{
angular
.
mock
.
module
(
'app'
);
angular
.
mock
.
module
(
'h.templates'
);
});
describe
(
'read only state'
,
function
()
{
it
(
'should show the rendered view when readOnly is true'
,
function
()
{
var
editor
=
util
.
createDirective
(
document
,
'markdown'
,
{
readOnly
:
true
,
ngModel
:
'Hello World'
,
});
assert
.
isTrue
(
isHidden
(
inputElement
(
editor
)));
assert
.
isFalse
(
isHidden
(
viewElement
(
editor
)));
});
it
(
'should show the editor when readOnly is false'
,
function
()
{
var
editor
=
util
.
createDirective
(
document
,
'markdown'
,
{
readOnly
:
false
,
ngModel
:
'Hello World'
,
});
assert
.
isFalse
(
isHidden
(
inputElement
(
editor
)));
assert
.
isTrue
(
isHidden
(
viewElement
(
editor
)));
});
});
describe
(
'rendering'
,
function
()
{
it
(
'should render input markdown'
,
function
()
{
var
editor
=
util
.
createDirective
(
document
,
'markdown'
,
{
readOnly
:
true
,
ngModel
:
'Hello World'
,
});
assert
.
equal
(
getRenderedHTML
(
editor
),
'rendered:Hello World'
);
});
});
describe
(
'math rendering'
,
function
()
{
it
(
'should render LaTeX'
,
function
()
{
var
editor
=
util
.
createDirective
(
document
,
'markdown'
,
{
readOnly
:
true
,
ngModel
:
'$$x*2$$'
,
});
assert
.
equal
(
getRenderedHTML
(
editor
),
'rendered:math:
\\
displaystyle {x*2}rendered:'
);
});
});
});
h/static/scripts/karma.config.js
View file @
2dc90311
...
...
@@ -59,6 +59,7 @@ module.exports = function(config) {
browserify
:
{
debug
:
true
,
extensions
:
[
'.coffee'
],
noParse
:
[
require
.
resolve
(
'./vendor/katex'
)],
configure
:
function
(
bundle
)
{
bundle
.
transform
(
'coffeeify'
)
...
...
h/static/scripts/test/bootstrap.js
View file @
2dc90311
...
...
@@ -10,5 +10,6 @@ sinon.assert.expose(assert, {prefix: null});
//
window
.
jQuery
=
window
.
$
=
require
(
'jquery'
);
require
(
'angular'
);
require
(
'angular-resource'
);
require
(
'angular-mocks'
);
require
(
'angular-resource'
);
require
(
'angular-sanitize'
);
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment