milksteak 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. data/.gitignore +1 -14
  2. data/.rspec +2 -0
  3. data/Gemfile +1 -1
  4. data/Guardfile +8 -0
  5. data/Rakefile +0 -1
  6. data/Readme.md +37 -0
  7. data/lib/helpers.rb +0 -0
  8. data/lib/milksteak/cms.rb +17 -0
  9. data/lib/milksteak/version.rb +1 -1
  10. data/lib/milksteak.rb +52 -1
  11. data/lib/models/page.rb +73 -0
  12. data/lib/models/user.rb +5 -0
  13. data/lib/public/milksteak/codemirror2/keymap/emacs.js +29 -0
  14. data/lib/public/milksteak/codemirror2/keymap/vim.js +347 -0
  15. data/lib/public/milksteak/codemirror2/lib/codemirror.css +110 -0
  16. data/lib/public/milksteak/codemirror2/lib/codemirror.js +2887 -0
  17. data/lib/public/milksteak/codemirror2/lib/util/dialog.css +23 -0
  18. data/lib/public/milksteak/codemirror2/lib/util/dialog.js +63 -0
  19. data/lib/public/milksteak/codemirror2/lib/util/foldcode.js +186 -0
  20. data/lib/public/milksteak/codemirror2/lib/util/formatting.js +294 -0
  21. data/lib/public/milksteak/codemirror2/lib/util/javascript-hint.js +132 -0
  22. data/lib/public/milksteak/codemirror2/lib/util/match-highlighter.js +44 -0
  23. data/lib/public/milksteak/codemirror2/lib/util/overlay.js +51 -0
  24. data/lib/public/milksteak/codemirror2/lib/util/runmode.js +49 -0
  25. data/lib/public/milksteak/codemirror2/lib/util/search.js +114 -0
  26. data/lib/public/milksteak/codemirror2/lib/util/searchcursor.js +117 -0
  27. data/lib/public/milksteak/codemirror2/lib/util/simple-hint.css +16 -0
  28. data/lib/public/milksteak/codemirror2/lib/util/simple-hint.js +66 -0
  29. data/lib/public/milksteak/codemirror2/mode/clike/clike.js +234 -0
  30. data/lib/public/milksteak/codemirror2/mode/clike/index.html +101 -0
  31. data/lib/public/milksteak/codemirror2/mode/clojure/clojure.js +207 -0
  32. data/lib/public/milksteak/codemirror2/mode/clojure/index.html +66 -0
  33. data/lib/public/milksteak/codemirror2/mode/coffeescript/LICENSE +22 -0
  34. data/lib/public/milksteak/codemirror2/mode/coffeescript/coffeescript.js +341 -0
  35. data/lib/public/milksteak/codemirror2/mode/coffeescript/index.html +721 -0
  36. data/lib/public/milksteak/codemirror2/mode/css/css.js +124 -0
  37. data/lib/public/milksteak/codemirror2/mode/css/index.html +55 -0
  38. data/lib/public/milksteak/codemirror2/mode/diff/diff.css +3 -0
  39. data/lib/public/milksteak/codemirror2/mode/diff/diff.js +13 -0
  40. data/lib/public/milksteak/codemirror2/mode/diff/index.html +99 -0
  41. data/lib/public/milksteak/codemirror2/mode/ecl/ecl.js +203 -0
  42. data/lib/public/milksteak/codemirror2/mode/ecl/index.html +42 -0
  43. data/lib/public/milksteak/codemirror2/mode/gfm/gfm.js +108 -0
  44. data/lib/public/milksteak/codemirror2/mode/gfm/index.html +47 -0
  45. data/lib/public/milksteak/codemirror2/mode/go/go.js +170 -0
  46. data/lib/public/milksteak/codemirror2/mode/go/index.html +72 -0
  47. data/lib/public/milksteak/codemirror2/mode/groovy/groovy.js +210 -0
  48. data/lib/public/milksteak/codemirror2/mode/groovy/index.html +71 -0
  49. data/lib/public/milksteak/codemirror2/mode/haskell/haskell.js +242 -0
  50. data/lib/public/milksteak/codemirror2/mode/haskell/index.html +60 -0
  51. data/lib/public/milksteak/codemirror2/mode/htmlembedded/htmlembedded.js +68 -0
  52. data/lib/public/milksteak/codemirror2/mode/htmlembedded/index.html +49 -0
  53. data/lib/public/milksteak/codemirror2/mode/htmlmixed/htmlmixed.js +83 -0
  54. data/lib/public/milksteak/codemirror2/mode/htmlmixed/index.html +51 -0
  55. data/lib/public/milksteak/codemirror2/mode/javascript/index.html +77 -0
  56. data/lib/public/milksteak/codemirror2/mode/javascript/javascript.js +360 -0
  57. data/lib/public/milksteak/codemirror2/mode/jinja2/index.html +37 -0
  58. data/lib/public/milksteak/codemirror2/mode/jinja2/jinja2.js +42 -0
  59. data/lib/public/milksteak/codemirror2/mode/less/index.html +577 -0
  60. data/lib/public/milksteak/codemirror2/mode/less/less.js +186 -0
  61. data/lib/public/milksteak/codemirror2/mode/lua/index.html +72 -0
  62. data/lib/public/milksteak/codemirror2/mode/lua/lua.js +140 -0
  63. data/lib/public/milksteak/codemirror2/mode/markdown/index.html +339 -0
  64. data/lib/public/milksteak/codemirror2/mode/markdown/markdown.js +276 -0
  65. data/lib/public/milksteak/codemirror2/mode/mysql/index.html +41 -0
  66. data/lib/public/milksteak/codemirror2/mode/mysql/mysql.js +188 -0
  67. data/lib/public/milksteak/codemirror2/mode/ntriples/index.html +32 -0
  68. data/lib/public/milksteak/codemirror2/mode/ntriples/ntriples.js +172 -0
  69. data/lib/public/milksteak/codemirror2/mode/pascal/LICENSE +7 -0
  70. data/lib/public/milksteak/codemirror2/mode/pascal/index.html +48 -0
  71. data/lib/public/milksteak/codemirror2/mode/pascal/pascal.js +94 -0
  72. data/lib/public/milksteak/codemirror2/mode/perl/LICENSE +19 -0
  73. data/lib/public/milksteak/codemirror2/mode/perl/index.html +62 -0
  74. data/lib/public/milksteak/codemirror2/mode/perl/perl.js +816 -0
  75. data/lib/public/milksteak/codemirror2/mode/php/index.html +48 -0
  76. data/lib/public/milksteak/codemirror2/mode/php/php.js +150 -0
  77. data/lib/public/milksteak/codemirror2/mode/plsql/index.html +62 -0
  78. data/lib/public/milksteak/codemirror2/mode/plsql/plsql.js +217 -0
  79. data/lib/public/milksteak/codemirror2/mode/properties/index.html +40 -0
  80. data/lib/public/milksteak/codemirror2/mode/properties/properties.css +3 -0
  81. data/lib/public/milksteak/codemirror2/mode/properties/properties.js +57 -0
  82. data/lib/public/milksteak/codemirror2/mode/python/LICENSE.txt +21 -0
  83. data/lib/public/milksteak/codemirror2/mode/python/index.html +122 -0
  84. data/lib/public/milksteak/codemirror2/mode/python/python.js +340 -0
  85. data/lib/public/milksteak/codemirror2/mode/r/LICENSE +24 -0
  86. data/lib/public/milksteak/codemirror2/mode/r/index.html +73 -0
  87. data/lib/public/milksteak/codemirror2/mode/r/r.js +141 -0
  88. data/lib/public/milksteak/codemirror2/mode/rpm/changes/changes.js +19 -0
  89. data/lib/public/milksteak/codemirror2/mode/rpm/changes/index.html +53 -0
  90. data/lib/public/milksteak/codemirror2/mode/rpm/spec/index.html +99 -0
  91. data/lib/public/milksteak/codemirror2/mode/rpm/spec/spec.css +5 -0
  92. data/lib/public/milksteak/codemirror2/mode/rpm/spec/spec.js +66 -0
  93. data/lib/public/milksteak/codemirror2/mode/rst/index.html +525 -0
  94. data/lib/public/milksteak/codemirror2/mode/rst/rst.js +326 -0
  95. data/lib/public/milksteak/codemirror2/mode/ruby/LICENSE +24 -0
  96. data/lib/public/milksteak/codemirror2/mode/ruby/index.html +171 -0
  97. data/lib/public/milksteak/codemirror2/mode/ruby/ruby.js +200 -0
  98. data/lib/public/milksteak/codemirror2/mode/rust/index.html +48 -0
  99. data/lib/public/milksteak/codemirror2/mode/rust/rust.js +432 -0
  100. data/lib/public/milksteak/codemirror2/mode/scheme/index.html +64 -0
  101. data/lib/public/milksteak/codemirror2/mode/scheme/scheme.js +202 -0
  102. data/lib/public/milksteak/codemirror2/mode/smalltalk/index.html +55 -0
  103. data/lib/public/milksteak/codemirror2/mode/smalltalk/smalltalk.js +139 -0
  104. data/lib/public/milksteak/codemirror2/mode/sparql/index.html +40 -0
  105. data/lib/public/milksteak/codemirror2/mode/sparql/sparql.js +143 -0
  106. data/lib/public/milksteak/codemirror2/mode/stex/index.html +95 -0
  107. data/lib/public/milksteak/codemirror2/mode/stex/stex.js +167 -0
  108. data/lib/public/milksteak/codemirror2/mode/tiddlywiki/index.html +183 -0
  109. data/lib/public/milksteak/codemirror2/mode/tiddlywiki/tiddlywiki.css +21 -0
  110. data/lib/public/milksteak/codemirror2/mode/tiddlywiki/tiddlywiki.js +374 -0
  111. data/lib/public/milksteak/codemirror2/mode/velocity/index.html +103 -0
  112. data/lib/public/milksteak/codemirror2/mode/velocity/velocity.js +146 -0
  113. data/lib/public/milksteak/codemirror2/mode/verilog/index.html +210 -0
  114. data/lib/public/milksteak/codemirror2/mode/verilog/verilog.js +194 -0
  115. data/lib/public/milksteak/codemirror2/mode/xml/index.html +45 -0
  116. data/lib/public/milksteak/codemirror2/mode/xml/xml.js +267 -0
  117. data/lib/public/milksteak/codemirror2/mode/xmlpure/index.html +59 -0
  118. data/lib/public/milksteak/codemirror2/mode/xmlpure/xmlpure.js +490 -0
  119. data/lib/public/milksteak/codemirror2/mode/yaml/index.html +67 -0
  120. data/lib/public/milksteak/codemirror2/mode/yaml/yaml.js +95 -0
  121. data/lib/public/milksteak/codemirror2/theme/cobalt.css +18 -0
  122. data/lib/public/milksteak/codemirror2/theme/eclipse.css +25 -0
  123. data/lib/public/milksteak/codemirror2/theme/elegant.css +10 -0
  124. data/lib/public/milksteak/codemirror2/theme/monokai.css +28 -0
  125. data/lib/public/milksteak/codemirror2/theme/neat.css +9 -0
  126. data/lib/public/milksteak/codemirror2/theme/night.css +21 -0
  127. data/lib/public/milksteak/codemirror2/theme/rubyblue.css +21 -0
  128. data/lib/public/milksteak/css/bootstrap-responsive.css +567 -0
  129. data/lib/public/milksteak/css/bootstrap-responsive.min.css +3 -0
  130. data/lib/public/milksteak/css/bootstrap.css +3365 -0
  131. data/lib/public/milksteak/css/bootstrap.min.css +610 -0
  132. data/lib/public/milksteak/css/calendrical.css +69 -0
  133. data/lib/public/milksteak/css/prettify.css +52 -0
  134. data/lib/public/milksteak/css/style.css +8 -0
  135. data/lib/public/milksteak/img/glyphicons-halflings-white.png +0 -0
  136. data/lib/public/milksteak/img/glyphicons-halflings.png +0 -0
  137. data/lib/public/milksteak/img/spinner.gif +0 -0
  138. data/lib/public/milksteak/jqueryFileTree/connectors/jqueryFileTree.asp +44 -0
  139. data/lib/public/milksteak/jqueryFileTree/connectors/jqueryFileTree.aspx +31 -0
  140. data/lib/public/milksteak/jqueryFileTree/connectors/jqueryFileTree.cf +19 -0
  141. data/lib/public/milksteak/jqueryFileTree/connectors/jqueryFileTree.jsp +49 -0
  142. data/lib/public/milksteak/jqueryFileTree/connectors/jqueryFileTree.php +43 -0
  143. data/lib/public/milksteak/jqueryFileTree/connectors/jqueryFileTree.pl +102 -0
  144. data/lib/public/milksteak/jqueryFileTree/connectors/jqueryFileTree.py +25 -0
  145. data/lib/public/milksteak/jqueryFileTree/connectors/jqueryFileTree.rb +61 -0
  146. data/lib/public/milksteak/jqueryFileTree/connectors/jqueryFileTree_huck.lasso +36 -0
  147. data/lib/public/milksteak/jqueryFileTree/connectors/jqueryFileTree_sabourdin.lasso +48 -0
  148. data/lib/public/milksteak/jqueryFileTree/images/application.png +0 -0
  149. data/lib/public/milksteak/jqueryFileTree/images/code.png +0 -0
  150. data/lib/public/milksteak/jqueryFileTree/images/css.png +0 -0
  151. data/lib/public/milksteak/jqueryFileTree/images/db.png +0 -0
  152. data/lib/public/milksteak/jqueryFileTree/images/directory.png +0 -0
  153. data/lib/public/milksteak/jqueryFileTree/images/doc.png +0 -0
  154. data/lib/public/milksteak/jqueryFileTree/images/file.png +0 -0
  155. data/lib/public/milksteak/jqueryFileTree/images/film.png +0 -0
  156. data/lib/public/milksteak/jqueryFileTree/images/flash.png +0 -0
  157. data/lib/public/milksteak/jqueryFileTree/images/folder_open.png +0 -0
  158. data/lib/public/milksteak/jqueryFileTree/images/html.png +0 -0
  159. data/lib/public/milksteak/jqueryFileTree/images/java.png +0 -0
  160. data/lib/public/milksteak/jqueryFileTree/images/linux.png +0 -0
  161. data/lib/public/milksteak/jqueryFileTree/images/music.png +0 -0
  162. data/lib/public/milksteak/jqueryFileTree/images/pdf.png +0 -0
  163. data/lib/public/milksteak/jqueryFileTree/images/php.png +0 -0
  164. data/lib/public/milksteak/jqueryFileTree/images/picture.png +0 -0
  165. data/lib/public/milksteak/jqueryFileTree/images/ppt.png +0 -0
  166. data/lib/public/milksteak/jqueryFileTree/images/psd.png +0 -0
  167. data/lib/public/milksteak/jqueryFileTree/images/ruby.png +0 -0
  168. data/lib/public/milksteak/jqueryFileTree/images/script.png +0 -0
  169. data/lib/public/milksteak/jqueryFileTree/images/spinner.gif +0 -0
  170. data/lib/public/milksteak/jqueryFileTree/images/txt.png +0 -0
  171. data/lib/public/milksteak/jqueryFileTree/images/xls.png +0 -0
  172. data/lib/public/milksteak/jqueryFileTree/images/zip.png +0 -0
  173. data/lib/public/milksteak/jqueryFileTree/jqueryFileTree.css +91 -0
  174. data/lib/public/milksteak/jqueryFileTree/jqueryFileTree.js +95 -0
  175. data/lib/public/milksteak/js/app.js +0 -0
  176. data/lib/public/milksteak/js/backbone.js +1290 -0
  177. data/lib/public/milksteak/js/bootstrap-alert.js +91 -0
  178. data/lib/public/milksteak/js/bootstrap-button.js +98 -0
  179. data/lib/public/milksteak/js/bootstrap-carousel.js +154 -0
  180. data/lib/public/milksteak/js/bootstrap-collapse.js +136 -0
  181. data/lib/public/milksteak/js/bootstrap-dropdown.js +92 -0
  182. data/lib/public/milksteak/js/bootstrap-modal.js +209 -0
  183. data/lib/public/milksteak/js/bootstrap-popover.js +95 -0
  184. data/lib/public/milksteak/js/bootstrap-scrollspy.js +125 -0
  185. data/lib/public/milksteak/js/bootstrap-tab.js +130 -0
  186. data/lib/public/milksteak/js/bootstrap-tooltip.js +270 -0
  187. data/lib/public/milksteak/js/bootstrap-transition.js +51 -0
  188. data/lib/public/milksteak/js/bootstrap-typeahead.js +271 -0
  189. data/lib/public/milksteak/js/bootstrap.js +1722 -0
  190. data/lib/public/milksteak/js/bootstrap.min.js +1 -0
  191. data/lib/public/milksteak/js/jquery-ui.min.js +15 -0
  192. data/lib/public/milksteak/js/jquery.calendrical.js +490 -0
  193. data/lib/public/milksteak/js/jquery.easing.js +146 -0
  194. data/lib/public/milksteak/js/jquery.js +9252 -0
  195. data/lib/public/milksteak/js/mustache.js +536 -0
  196. data/lib/public/milksteak/js/prettify.js +28 -0
  197. data/lib/public/milksteak/js/underscore.js +999 -0
  198. data/lib/public/milksteak/markitup/.DS_Store +0 -0
  199. data/lib/public/milksteak/markitup/jquery.markitup.js +593 -0
  200. data/lib/public/milksteak/markitup/sets/default/images/bold.png +0 -0
  201. data/lib/public/milksteak/markitup/sets/default/images/clean.png +0 -0
  202. data/lib/public/milksteak/markitup/sets/default/images/image.png +0 -0
  203. data/lib/public/milksteak/markitup/sets/default/images/italic.png +0 -0
  204. data/lib/public/milksteak/markitup/sets/default/images/link.png +0 -0
  205. data/lib/public/milksteak/markitup/sets/default/images/list-bullet.png +0 -0
  206. data/lib/public/milksteak/markitup/sets/default/images/list-numeric.png +0 -0
  207. data/lib/public/milksteak/markitup/sets/default/images/picture.png +0 -0
  208. data/lib/public/milksteak/markitup/sets/default/images/preview.png +0 -0
  209. data/lib/public/milksteak/markitup/sets/default/images/stroke.png +0 -0
  210. data/lib/public/milksteak/markitup/sets/default/set.js +30 -0
  211. data/lib/public/milksteak/markitup/sets/default/style.css +34 -0
  212. data/lib/public/milksteak/markitup/sets/textile/images/bold.png +0 -0
  213. data/lib/public/milksteak/markitup/sets/textile/images/code.png +0 -0
  214. data/lib/public/milksteak/markitup/sets/textile/images/h1.png +0 -0
  215. data/lib/public/milksteak/markitup/sets/textile/images/h2.png +0 -0
  216. data/lib/public/milksteak/markitup/sets/textile/images/h3.png +0 -0
  217. data/lib/public/milksteak/markitup/sets/textile/images/h4.png +0 -0
  218. data/lib/public/milksteak/markitup/sets/textile/images/h5.png +0 -0
  219. data/lib/public/milksteak/markitup/sets/textile/images/h6.png +0 -0
  220. data/lib/public/milksteak/markitup/sets/textile/images/italic.png +0 -0
  221. data/lib/public/milksteak/markitup/sets/textile/images/link.png +0 -0
  222. data/lib/public/milksteak/markitup/sets/textile/images/list-bullet.png +0 -0
  223. data/lib/public/milksteak/markitup/sets/textile/images/list-numeric.png +0 -0
  224. data/lib/public/milksteak/markitup/sets/textile/images/paragraph.png +0 -0
  225. data/lib/public/milksteak/markitup/sets/textile/images/picture.png +0 -0
  226. data/lib/public/milksteak/markitup/sets/textile/images/preview.png +0 -0
  227. data/lib/public/milksteak/markitup/sets/textile/images/quotes.png +0 -0
  228. data/lib/public/milksteak/markitup/sets/textile/images/stroke.png +0 -0
  229. data/lib/public/milksteak/markitup/sets/textile/set.js +33 -0
  230. data/lib/public/milksteak/markitup/sets/textile/style.css +60 -0
  231. data/lib/public/milksteak/markitup/skins/markitup/images/bg-container.png +0 -0
  232. data/lib/public/milksteak/markitup/skins/markitup/images/bg-editor-bbcode.png +0 -0
  233. data/lib/public/milksteak/markitup/skins/markitup/images/bg-editor-dotclear.png +0 -0
  234. data/lib/public/milksteak/markitup/skins/markitup/images/bg-editor-html.png +0 -0
  235. data/lib/public/milksteak/markitup/skins/markitup/images/bg-editor-json.png +0 -0
  236. data/lib/public/milksteak/markitup/skins/markitup/images/bg-editor-markdown.png +0 -0
  237. data/lib/public/milksteak/markitup/skins/markitup/images/bg-editor-textile.png +0 -0
  238. data/lib/public/milksteak/markitup/skins/markitup/images/bg-editor-wiki.png +0 -0
  239. data/lib/public/milksteak/markitup/skins/markitup/images/bg-editor-xml.png +0 -0
  240. data/lib/public/milksteak/markitup/skins/markitup/images/bg-editor.png +0 -0
  241. data/lib/public/milksteak/markitup/skins/markitup/images/handle.png +0 -0
  242. data/lib/public/milksteak/markitup/skins/markitup/images/menu.png +0 -0
  243. data/lib/public/milksteak/markitup/skins/markitup/images/submenu.png +0 -0
  244. data/lib/public/milksteak/markitup/skins/markitup/style.css +147 -0
  245. data/lib/public/milksteak/markitup/skins/simple/images/handle.png +0 -0
  246. data/lib/public/milksteak/markitup/skins/simple/images/menu.png +0 -0
  247. data/lib/public/milksteak/markitup/skins/simple/images/submenu.png +0 -0
  248. data/lib/public/milksteak/markitup/skins/simple/style.css +118 -0
  249. data/lib/public/milksteak/markitup/templates/preview.css +5 -0
  250. data/lib/public/milksteak/markitup/templates/preview.html +11 -0
  251. data/lib/public/milksteak/uploadify-v2.1.4/cancel.png +0 -0
  252. data/lib/public/milksteak/uploadify-v2.1.4/check.php +35 -0
  253. data/lib/public/milksteak/uploadify-v2.1.4/expressInstall.swf +0 -0
  254. data/lib/public/milksteak/uploadify-v2.1.4/jquery-1.4.2.min.js +154 -0
  255. data/lib/public/milksteak/uploadify-v2.1.4/jquery.uploadify.v2.1.4.js +296 -0
  256. data/lib/public/milksteak/uploadify-v2.1.4/jquery.uploadify.v2.1.4.min.js +26 -0
  257. data/lib/public/milksteak/uploadify-v2.1.4/swfobject.js +4 -0
  258. data/lib/public/milksteak/uploadify-v2.1.4/uploadify.allglyphs.swf +0 -0
  259. data/lib/public/milksteak/uploadify-v2.1.4/uploadify.css +53 -0
  260. data/lib/public/milksteak/uploadify-v2.1.4/uploadify.fla +0 -0
  261. data/lib/public/milksteak/uploadify-v2.1.4/uploadify.php +46 -0
  262. data/lib/public/milksteak/uploadify-v2.1.4/uploadify.swf +0 -0
  263. data/lib/public/milksteak/wmd/Markdown.Converter.js +1332 -0
  264. data/lib/public/milksteak/wmd/Markdown.Editor.js +2160 -0
  265. data/lib/public/milksteak/wmd/Markdown.Sanitizer.js +108 -0
  266. data/lib/public/milksteak/wmd/wmd-buttons.png +0 -0
  267. data/lib/public/milksteak/wmd/wmd.css +103 -0
  268. data/lib/public/milksteak/wymeditor/iframe/default/lbl-blockquote.png +0 -0
  269. data/lib/public/milksteak/wymeditor/iframe/default/lbl-h1.png +0 -0
  270. data/lib/public/milksteak/wymeditor/iframe/default/lbl-h2.png +0 -0
  271. data/lib/public/milksteak/wymeditor/iframe/default/lbl-h3.png +0 -0
  272. data/lib/public/milksteak/wymeditor/iframe/default/lbl-h4.png +0 -0
  273. data/lib/public/milksteak/wymeditor/iframe/default/lbl-h5.png +0 -0
  274. data/lib/public/milksteak/wymeditor/iframe/default/lbl-h6.png +0 -0
  275. data/lib/public/milksteak/wymeditor/iframe/default/lbl-p.png +0 -0
  276. data/lib/public/milksteak/wymeditor/iframe/default/lbl-pre.png +0 -0
  277. data/lib/public/milksteak/wymeditor/iframe/default/wymiframe.css +90 -0
  278. data/lib/public/milksteak/wymeditor/iframe/default/wymiframe.html +26 -0
  279. data/lib/public/milksteak/wymeditor/jquery.wymeditor.js +4688 -0
  280. data/lib/public/milksteak/wymeditor/jquery.wymeditor.min.js +1 -0
  281. data/lib/public/milksteak/wymeditor/jquery.wymeditor.pack.js +1 -0
  282. data/lib/public/milksteak/wymeditor/lang/bg.js +45 -0
  283. data/lib/public/milksteak/wymeditor/lang/ca.js +45 -0
  284. data/lib/public/milksteak/wymeditor/lang/cs.js +45 -0
  285. data/lib/public/milksteak/wymeditor/lang/cy.js +45 -0
  286. data/lib/public/milksteak/wymeditor/lang/de.js +45 -0
  287. data/lib/public/milksteak/wymeditor/lang/en.js +45 -0
  288. data/lib/public/milksteak/wymeditor/lang/es.js +45 -0
  289. data/lib/public/milksteak/wymeditor/lang/fa.js +46 -0
  290. data/lib/public/milksteak/wymeditor/lang/fi.js +44 -0
  291. data/lib/public/milksteak/wymeditor/lang/fr.js +45 -0
  292. data/lib/public/milksteak/wymeditor/lang/gl.js +45 -0
  293. data/lib/public/milksteak/wymeditor/lang/he.js +45 -0
  294. data/lib/public/milksteak/wymeditor/lang/hr.js +45 -0
  295. data/lib/public/milksteak/wymeditor/lang/hu.js +45 -0
  296. data/lib/public/milksteak/wymeditor/lang/it.js +45 -0
  297. data/lib/public/milksteak/wymeditor/lang/nb.js +45 -0
  298. data/lib/public/milksteak/wymeditor/lang/nl.js +45 -0
  299. data/lib/public/milksteak/wymeditor/lang/nn.js +45 -0
  300. data/lib/public/milksteak/wymeditor/lang/pl.js +45 -0
  301. data/lib/public/milksteak/wymeditor/lang/pt-br.js +45 -0
  302. data/lib/public/milksteak/wymeditor/lang/pt.js +45 -0
  303. data/lib/public/milksteak/wymeditor/lang/ru.js +45 -0
  304. data/lib/public/milksteak/wymeditor/lang/sv.js +45 -0
  305. data/lib/public/milksteak/wymeditor/lang/tr.js +45 -0
  306. data/lib/public/milksteak/wymeditor/lang/zh_cn.js +47 -0
  307. data/lib/public/milksteak/wymeditor/plugins/embed/jquery.wymeditor.embed.js +52 -0
  308. data/lib/public/milksteak/wymeditor/plugins/fullscreen/icon_fullscreen.gif +0 -0
  309. data/lib/public/milksteak/wymeditor/plugins/fullscreen/jquery.wymeditor.fullscreen.js +127 -0
  310. data/lib/public/milksteak/wymeditor/plugins/hovertools/jquery.wymeditor.hovertools.js +57 -0
  311. data/lib/public/milksteak/wymeditor/plugins/resizable/jquery.wymeditor.resizable.js +91 -0
  312. data/lib/public/milksteak/wymeditor/plugins/resizable/readme.txt +124 -0
  313. data/lib/public/milksteak/wymeditor/plugins/tidy/README +19 -0
  314. data/lib/public/milksteak/wymeditor/plugins/tidy/jquery.wymeditor.tidy.js +82 -0
  315. data/lib/public/milksteak/wymeditor/plugins/tidy/tidy.php +36 -0
  316. data/lib/public/milksteak/wymeditor/plugins/tidy/wand.png +0 -0
  317. data/lib/public/milksteak/wymeditor/skins/compact/icons.png +0 -0
  318. data/lib/public/milksteak/wymeditor/skins/compact/skin.css +134 -0
  319. data/lib/public/milksteak/wymeditor/skins/compact/skin.js +35 -0
  320. data/lib/public/milksteak/wymeditor/skins/default/icons.png +0 -0
  321. data/lib/public/milksteak/wymeditor/skins/default/skin.css +133 -0
  322. data/lib/public/milksteak/wymeditor/skins/default/skin.js +40 -0
  323. data/lib/public/milksteak/wymeditor/skins/minimal/images/bg.header.gif +0 -0
  324. data/lib/public/milksteak/wymeditor/skins/minimal/images/bg.selector.silver.gif +0 -0
  325. data/lib/public/milksteak/wymeditor/skins/minimal/images/bg.wymeditor.png +0 -0
  326. data/lib/public/milksteak/wymeditor/skins/minimal/images/icons.silver.gif +0 -0
  327. data/lib/public/milksteak/wymeditor/skins/minimal/skin.css +131 -0
  328. data/lib/public/milksteak/wymeditor/skins/minimal/skin.js +30 -0
  329. data/lib/public/milksteak/wymeditor/skins/silver/COPYING +674 -0
  330. data/lib/public/milksteak/wymeditor/skins/silver/README +27 -0
  331. data/lib/public/milksteak/wymeditor/skins/silver/images/bg.header.gif +0 -0
  332. data/lib/public/milksteak/wymeditor/skins/silver/images/bg.selector.silver.gif +0 -0
  333. data/lib/public/milksteak/wymeditor/skins/silver/images/bg.wymeditor.png +0 -0
  334. data/lib/public/milksteak/wymeditor/skins/silver/images/icons.silver.gif +0 -0
  335. data/lib/public/milksteak/wymeditor/skins/silver/skin.css +297 -0
  336. data/lib/public/milksteak/wymeditor/skins/silver/skin.js +61 -0
  337. data/lib/public/milksteak/wymeditor/skins/twopanels/icons.png +0 -0
  338. data/lib/public/milksteak/wymeditor/skins/twopanels/skin.css +134 -0
  339. data/lib/public/milksteak/wymeditor/skins/twopanels/skin.js +39 -0
  340. data/lib/public/milksteak/wymeditor/skins/wymeditor_icon.png +0 -0
  341. data/lib/views/layouts/admin.erb +8 -0
  342. data/lib/views/login.erb +1 -0
  343. data/milksteak.gemspec +30 -13
  344. data/spec/fixtures/pages/sample_page.yml +5 -0
  345. data/spec/lib/milksteak/cms_spec.rb +12 -0
  346. data/spec/models/page_spec.rb +58 -0
  347. data/spec/spec_helper.rb +19 -0
  348. metadata +429 -10
  349. data/LICENSE +0 -22
  350. data/README.md +0 -29
@@ -0,0 +1,2160 @@
1
+ // needs Markdown.Converter.js at the moment
2
+
3
+ (function () {
4
+
5
+ var util = {},
6
+ position = {},
7
+ ui = {},
8
+ doc = window.document,
9
+ re = window.RegExp,
10
+ nav = window.navigator,
11
+ SETTINGS = { lineLength: 72 },
12
+
13
+ // Used to work around some browser bugs where we can't use feature testing.
14
+ uaSniffed = {
15
+ isIE: /msie/.test(nav.userAgent.toLowerCase()),
16
+ isIE_5or6: /msie 6/.test(nav.userAgent.toLowerCase()) || /msie 5/.test(nav.userAgent.toLowerCase()),
17
+ isOpera: /opera/.test(nav.userAgent.toLowerCase())
18
+ };
19
+
20
+
21
+ // -------------------------------------------------------------------
22
+ // YOUR CHANGES GO HERE
23
+ //
24
+ // I've tried to localize the things you are likely to change to
25
+ // this area.
26
+ // -------------------------------------------------------------------
27
+
28
+ // The text that appears on the upper part of the dialog box when
29
+ // entering links.
30
+ var linkDialogText = "<p><b>Insert Hyperlink</b></p><p>http://example.com/ \"optional title\"</p>";
31
+ var imageDialogText = "<p><b>Insert Image</b></p><p>http://example.com/images/diagram.jpg \"optional title\"<br><br>Need <a href='http://www.google.com/search?q=free+image+hosting' target='_blank'>free image hosting?</a></p>";
32
+
33
+ // The default text that appears in the dialog input box when entering
34
+ // links.
35
+ var imageDefaultText = "http://";
36
+ var linkDefaultText = "http://";
37
+
38
+ var defaultHelpHoverTitle = "Markdown Editing Help";
39
+
40
+ // -------------------------------------------------------------------
41
+ // END OF YOUR CHANGES
42
+ // -------------------------------------------------------------------
43
+
44
+ // help, if given, should have a property "handler", the click handler for the help button,
45
+ // and can have an optional property "title" for the button's tooltip (defaults to "Markdown Editing Help").
46
+ // If help isn't given, not help button is created.
47
+ //
48
+ // The constructed editor object has the methods:
49
+ // - getConverter() returns the markdown converter object that was passed to the constructor
50
+ // - run() actually starts the editor; should be called after all necessary plugins are registered. Calling this more than once is a no-op.
51
+ // - refreshPreview() forces the preview to be updated. This method is only available after run() was called.
52
+ Markdown.Editor = function (markdownConverter, idPostfix, help) {
53
+
54
+ idPostfix = idPostfix || "";
55
+
56
+ var hooks = this.hooks = new Markdown.HookCollection();
57
+ hooks.addNoop("onPreviewRefresh"); // called with no arguments after the preview has been refreshed
58
+ hooks.addNoop("postBlockquoteCreation"); // called with the user's selection *after* the blockquote was created; should return the actual to-be-inserted text
59
+ hooks.addFalse("insertImageDialog"); /* called with one parameter: a callback to be called with the URL of the image. If the application creates
60
+ * its own image insertion dialog, this hook should return true, and the callback should be called with the chosen
61
+ * image url (or null if the user cancelled). If this hook returns false, the default dialog will be used.
62
+ */
63
+
64
+ this.getConverter = function () { return markdownConverter; }
65
+
66
+ var that = this,
67
+ panels;
68
+
69
+ this.run = function () {
70
+ if (panels)
71
+ return; // already initialized
72
+
73
+ panels = new PanelCollection(idPostfix);
74
+ var commandManager = new CommandManager(hooks);
75
+ var previewManager = new PreviewManager(markdownConverter, panels, function () { hooks.onPreviewRefresh(); });
76
+ var undoManager, uiManager;
77
+
78
+ if (!/\?noundo/.test(doc.location.href)) {
79
+ undoManager = new UndoManager(function () {
80
+ previewManager.refresh();
81
+ if (uiManager) // not available on the first call
82
+ uiManager.setUndoRedoButtonStates();
83
+ }, panels);
84
+ this.textOperation = function (f) {
85
+ undoManager.setCommandMode();
86
+ f();
87
+ that.refreshPreview();
88
+ }
89
+ }
90
+
91
+ uiManager = new UIManager(idPostfix, panels, undoManager, previewManager, commandManager, help);
92
+ uiManager.setUndoRedoButtonStates();
93
+
94
+ var forceRefresh = that.refreshPreview = function () { previewManager.refresh(true); };
95
+
96
+ forceRefresh();
97
+ };
98
+
99
+ }
100
+
101
+ // before: contains all the text in the input box BEFORE the selection.
102
+ // after: contains all the text in the input box AFTER the selection.
103
+ function Chunks() { }
104
+
105
+ // startRegex: a regular expression to find the start tag
106
+ // endRegex: a regular expresssion to find the end tag
107
+ Chunks.prototype.findTags = function (startRegex, endRegex) {
108
+
109
+ var chunkObj = this;
110
+ var regex;
111
+
112
+ if (startRegex) {
113
+
114
+ regex = util.extendRegExp(startRegex, "", "$");
115
+
116
+ this.before = this.before.replace(regex,
117
+ function (match) {
118
+ chunkObj.startTag = chunkObj.startTag + match;
119
+ return "";
120
+ });
121
+
122
+ regex = util.extendRegExp(startRegex, "^", "");
123
+
124
+ this.selection = this.selection.replace(regex,
125
+ function (match) {
126
+ chunkObj.startTag = chunkObj.startTag + match;
127
+ return "";
128
+ });
129
+ }
130
+
131
+ if (endRegex) {
132
+
133
+ regex = util.extendRegExp(endRegex, "", "$");
134
+
135
+ this.selection = this.selection.replace(regex,
136
+ function (match) {
137
+ chunkObj.endTag = match + chunkObj.endTag;
138
+ return "";
139
+ });
140
+
141
+ regex = util.extendRegExp(endRegex, "^", "");
142
+
143
+ this.after = this.after.replace(regex,
144
+ function (match) {
145
+ chunkObj.endTag = match + chunkObj.endTag;
146
+ return "";
147
+ });
148
+ }
149
+ };
150
+
151
+ // If remove is false, the whitespace is transferred
152
+ // to the before/after regions.
153
+ //
154
+ // If remove is true, the whitespace disappears.
155
+ Chunks.prototype.trimWhitespace = function (remove) {
156
+ var beforeReplacer, afterReplacer, that = this;
157
+ if (remove) {
158
+ beforeReplacer = afterReplacer = "";
159
+ } else {
160
+ beforeReplacer = function (s) { that.before += s; return ""; }
161
+ afterReplacer = function (s) { that.after = s + that.after; return ""; }
162
+ }
163
+
164
+ this.selection = this.selection.replace(/^(\s*)/, beforeReplacer).replace(/(\s*)$/, afterReplacer);
165
+ };
166
+
167
+
168
+ Chunks.prototype.skipLines = function (nLinesBefore, nLinesAfter, findExtraNewlines) {
169
+
170
+ if (nLinesBefore === undefined) {
171
+ nLinesBefore = 1;
172
+ }
173
+
174
+ if (nLinesAfter === undefined) {
175
+ nLinesAfter = 1;
176
+ }
177
+
178
+ nLinesBefore++;
179
+ nLinesAfter++;
180
+
181
+ var regexText;
182
+ var replacementText;
183
+
184
+ // chrome bug ... documented at: http://meta.stackoverflow.com/questions/63307/blockquote-glitch-in-editor-in-chrome-6-and-7/65985#65985
185
+ if (navigator.userAgent.match(/Chrome/)) {
186
+ "X".match(/()./);
187
+ }
188
+
189
+ this.selection = this.selection.replace(/(^\n*)/, "");
190
+
191
+ this.startTag = this.startTag + re.$1;
192
+
193
+ this.selection = this.selection.replace(/(\n*$)/, "");
194
+ this.endTag = this.endTag + re.$1;
195
+ this.startTag = this.startTag.replace(/(^\n*)/, "");
196
+ this.before = this.before + re.$1;
197
+ this.endTag = this.endTag.replace(/(\n*$)/, "");
198
+ this.after = this.after + re.$1;
199
+
200
+ if (this.before) {
201
+
202
+ regexText = replacementText = "";
203
+
204
+ while (nLinesBefore--) {
205
+ regexText += "\\n?";
206
+ replacementText += "\n";
207
+ }
208
+
209
+ if (findExtraNewlines) {
210
+ regexText = "\\n*";
211
+ }
212
+ this.before = this.before.replace(new re(regexText + "$", ""), replacementText);
213
+ }
214
+
215
+ if (this.after) {
216
+
217
+ regexText = replacementText = "";
218
+
219
+ while (nLinesAfter--) {
220
+ regexText += "\\n?";
221
+ replacementText += "\n";
222
+ }
223
+ if (findExtraNewlines) {
224
+ regexText = "\\n*";
225
+ }
226
+
227
+ this.after = this.after.replace(new re(regexText, ""), replacementText);
228
+ }
229
+ };
230
+
231
+ // end of Chunks
232
+
233
+ // A collection of the important regions on the page.
234
+ // Cached so we don't have to keep traversing the DOM.
235
+ // Also holds ieCachedRange and ieCachedScrollTop, where necessary; working around
236
+ // this issue:
237
+ // Internet explorer has problems with CSS sprite buttons that use HTML
238
+ // lists. When you click on the background image "button", IE will
239
+ // select the non-existent link text and discard the selection in the
240
+ // textarea. The solution to this is to cache the textarea selection
241
+ // on the button's mousedown event and set a flag. In the part of the
242
+ // code where we need to grab the selection, we check for the flag
243
+ // and, if it's set, use the cached area instead of querying the
244
+ // textarea.
245
+ //
246
+ // This ONLY affects Internet Explorer (tested on versions 6, 7
247
+ // and 8) and ONLY on button clicks. Keyboard shortcuts work
248
+ // normally since the focus never leaves the textarea.
249
+ function PanelCollection(postfix) {
250
+ this.buttonBar = doc.getElementById("wmd-button-bar" + postfix);
251
+ this.preview = doc.getElementById("wmd-preview" + postfix);
252
+ this.input = doc.getElementById("wmd-input" + postfix);
253
+ };
254
+
255
+ // Returns true if the DOM element is visible, false if it's hidden.
256
+ // Checks if display is anything other than none.
257
+ util.isVisible = function (elem) {
258
+
259
+ if (window.getComputedStyle) {
260
+ // Most browsers
261
+ return window.getComputedStyle(elem, null).getPropertyValue("display") !== "none";
262
+ }
263
+ else if (elem.currentStyle) {
264
+ // IE
265
+ return elem.currentStyle["display"] !== "none";
266
+ }
267
+ };
268
+
269
+
270
+ // Adds a listener callback to a DOM element which is fired on a specified
271
+ // event.
272
+ util.addEvent = function (elem, event, listener) {
273
+ if (elem.attachEvent) {
274
+ // IE only. The "on" is mandatory.
275
+ elem.attachEvent("on" + event, listener);
276
+ }
277
+ else {
278
+ // Other browsers.
279
+ elem.addEventListener(event, listener, false);
280
+ }
281
+ };
282
+
283
+
284
+ // Removes a listener callback from a DOM element which is fired on a specified
285
+ // event.
286
+ util.removeEvent = function (elem, event, listener) {
287
+ if (elem.detachEvent) {
288
+ // IE only. The "on" is mandatory.
289
+ elem.detachEvent("on" + event, listener);
290
+ }
291
+ else {
292
+ // Other browsers.
293
+ elem.removeEventListener(event, listener, false);
294
+ }
295
+ };
296
+
297
+ // Converts \r\n and \r to \n.
298
+ util.fixEolChars = function (text) {
299
+ text = text.replace(/\r\n/g, "\n");
300
+ text = text.replace(/\r/g, "\n");
301
+ return text;
302
+ };
303
+
304
+ // Extends a regular expression. Returns a new RegExp
305
+ // using pre + regex + post as the expression.
306
+ // Used in a few functions where we have a base
307
+ // expression and we want to pre- or append some
308
+ // conditions to it (e.g. adding "$" to the end).
309
+ // The flags are unchanged.
310
+ //
311
+ // regex is a RegExp, pre and post are strings.
312
+ util.extendRegExp = function (regex, pre, post) {
313
+
314
+ if (pre === null || pre === undefined) {
315
+ pre = "";
316
+ }
317
+ if (post === null || post === undefined) {
318
+ post = "";
319
+ }
320
+
321
+ var pattern = regex.toString();
322
+ var flags;
323
+
324
+ // Replace the flags with empty space and store them.
325
+ pattern = pattern.replace(/\/([gim]*)$/, function (wholeMatch, flagsPart) {
326
+ flags = flagsPart;
327
+ return "";
328
+ });
329
+
330
+ // Remove the slash delimiters on the regular expression.
331
+ pattern = pattern.replace(/(^\/|\/$)/g, "");
332
+ pattern = pre + pattern + post;
333
+
334
+ return new re(pattern, flags);
335
+ }
336
+
337
+ // UNFINISHED
338
+ // The assignment in the while loop makes jslint cranky.
339
+ // I'll change it to a better loop later.
340
+ position.getTop = function (elem, isInner) {
341
+ var result = elem.offsetTop;
342
+ if (!isInner) {
343
+ while (elem = elem.offsetParent) {
344
+ result += elem.offsetTop;
345
+ }
346
+ }
347
+ return result;
348
+ };
349
+
350
+ position.getHeight = function (elem) {
351
+ return elem.offsetHeight || elem.scrollHeight;
352
+ };
353
+
354
+ position.getWidth = function (elem) {
355
+ return elem.offsetWidth || elem.scrollWidth;
356
+ };
357
+
358
+ position.getPageSize = function () {
359
+
360
+ var scrollWidth, scrollHeight;
361
+ var innerWidth, innerHeight;
362
+
363
+ // It's not very clear which blocks work with which browsers.
364
+ if (self.innerHeight && self.scrollMaxY) {
365
+ scrollWidth = doc.body.scrollWidth;
366
+ scrollHeight = self.innerHeight + self.scrollMaxY;
367
+ }
368
+ else if (doc.body.scrollHeight > doc.body.offsetHeight) {
369
+ scrollWidth = doc.body.scrollWidth;
370
+ scrollHeight = doc.body.scrollHeight;
371
+ }
372
+ else {
373
+ scrollWidth = doc.body.offsetWidth;
374
+ scrollHeight = doc.body.offsetHeight;
375
+ }
376
+
377
+ if (self.innerHeight) {
378
+ // Non-IE browser
379
+ innerWidth = self.innerWidth;
380
+ innerHeight = self.innerHeight;
381
+ }
382
+ else if (doc.documentElement && doc.documentElement.clientHeight) {
383
+ // Some versions of IE (IE 6 w/ a DOCTYPE declaration)
384
+ innerWidth = doc.documentElement.clientWidth;
385
+ innerHeight = doc.documentElement.clientHeight;
386
+ }
387
+ else if (doc.body) {
388
+ // Other versions of IE
389
+ innerWidth = doc.body.clientWidth;
390
+ innerHeight = doc.body.clientHeight;
391
+ }
392
+
393
+ var maxWidth = Math.max(scrollWidth, innerWidth);
394
+ var maxHeight = Math.max(scrollHeight, innerHeight);
395
+ return [maxWidth, maxHeight, innerWidth, innerHeight];
396
+ };
397
+
398
+ // Handles pushing and popping TextareaStates for undo/redo commands.
399
+ // I should rename the stack variables to list.
400
+ function UndoManager(callback, panels) {
401
+
402
+ var undoObj = this;
403
+ var undoStack = []; // A stack of undo states
404
+ var stackPtr = 0; // The index of the current state
405
+ var mode = "none";
406
+ var lastState; // The last state
407
+ var timer; // The setTimeout handle for cancelling the timer
408
+ var inputStateObj;
409
+
410
+ // Set the mode for later logic steps.
411
+ var setMode = function (newMode, noSave) {
412
+ if (mode != newMode) {
413
+ mode = newMode;
414
+ if (!noSave) {
415
+ saveState();
416
+ }
417
+ }
418
+
419
+ if (!uaSniffed.isIE || mode != "moving") {
420
+ timer = setTimeout(refreshState, 1);
421
+ }
422
+ else {
423
+ inputStateObj = null;
424
+ }
425
+ };
426
+
427
+ var refreshState = function (isInitialState) {
428
+ inputStateObj = new TextareaState(panels, isInitialState);
429
+ timer = undefined;
430
+ };
431
+
432
+ this.setCommandMode = function () {
433
+ mode = "command";
434
+ saveState();
435
+ timer = setTimeout(refreshState, 0);
436
+ };
437
+
438
+ this.canUndo = function () {
439
+ return stackPtr > 1;
440
+ };
441
+
442
+ this.canRedo = function () {
443
+ if (undoStack[stackPtr + 1]) {
444
+ return true;
445
+ }
446
+ return false;
447
+ };
448
+
449
+ // Removes the last state and restores it.
450
+ this.undo = function () {
451
+
452
+ if (undoObj.canUndo()) {
453
+ if (lastState) {
454
+ // What about setting state -1 to null or checking for undefined?
455
+ lastState.restore();
456
+ lastState = null;
457
+ }
458
+ else {
459
+ undoStack[stackPtr] = new TextareaState(panels);
460
+ undoStack[--stackPtr].restore();
461
+
462
+ if (callback) {
463
+ callback();
464
+ }
465
+ }
466
+ }
467
+
468
+ mode = "none";
469
+ panels.input.focus();
470
+ refreshState();
471
+ };
472
+
473
+ // Redo an action.
474
+ this.redo = function () {
475
+
476
+ if (undoObj.canRedo()) {
477
+
478
+ undoStack[++stackPtr].restore();
479
+
480
+ if (callback) {
481
+ callback();
482
+ }
483
+ }
484
+
485
+ mode = "none";
486
+ panels.input.focus();
487
+ refreshState();
488
+ };
489
+
490
+ // Push the input area state to the stack.
491
+ var saveState = function () {
492
+ var currState = inputStateObj || new TextareaState(panels);
493
+
494
+ if (!currState) {
495
+ return false;
496
+ }
497
+ if (mode == "moving") {
498
+ if (!lastState) {
499
+ lastState = currState;
500
+ }
501
+ return;
502
+ }
503
+ if (lastState) {
504
+ if (undoStack[stackPtr - 1].text != lastState.text) {
505
+ undoStack[stackPtr++] = lastState;
506
+ }
507
+ lastState = null;
508
+ }
509
+ undoStack[stackPtr++] = currState;
510
+ undoStack[stackPtr + 1] = null;
511
+ if (callback) {
512
+ callback();
513
+ }
514
+ };
515
+
516
+ var handleCtrlYZ = function (event) {
517
+
518
+ var handled = false;
519
+
520
+ if (event.ctrlKey || event.metaKey) {
521
+
522
+ // IE and Opera do not support charCode.
523
+ var keyCode = event.charCode || event.keyCode;
524
+ var keyCodeChar = String.fromCharCode(keyCode);
525
+
526
+ switch (keyCodeChar) {
527
+
528
+ case "y":
529
+ undoObj.redo();
530
+ handled = true;
531
+ break;
532
+
533
+ case "z":
534
+ if (!event.shiftKey) {
535
+ undoObj.undo();
536
+ }
537
+ else {
538
+ undoObj.redo();
539
+ }
540
+ handled = true;
541
+ break;
542
+ }
543
+ }
544
+
545
+ if (handled) {
546
+ if (event.preventDefault) {
547
+ event.preventDefault();
548
+ }
549
+ if (window.event) {
550
+ window.event.returnValue = false;
551
+ }
552
+ return;
553
+ }
554
+ };
555
+
556
+ // Set the mode depending on what is going on in the input area.
557
+ var handleModeChange = function (event) {
558
+
559
+ if (!event.ctrlKey && !event.metaKey) {
560
+
561
+ var keyCode = event.keyCode;
562
+
563
+ if ((keyCode >= 33 && keyCode <= 40) || (keyCode >= 63232 && keyCode <= 63235)) {
564
+ // 33 - 40: page up/dn and arrow keys
565
+ // 63232 - 63235: page up/dn and arrow keys on safari
566
+ setMode("moving");
567
+ }
568
+ else if (keyCode == 8 || keyCode == 46 || keyCode == 127) {
569
+ // 8: backspace
570
+ // 46: delete
571
+ // 127: delete
572
+ setMode("deleting");
573
+ }
574
+ else if (keyCode == 13) {
575
+ // 13: Enter
576
+ setMode("newlines");
577
+ }
578
+ else if (keyCode == 27) {
579
+ // 27: escape
580
+ setMode("escape");
581
+ }
582
+ else if ((keyCode < 16 || keyCode > 20) && keyCode != 91) {
583
+ // 16-20 are shift, etc.
584
+ // 91: left window key
585
+ // I think this might be a little messed up since there are
586
+ // a lot of nonprinting keys above 20.
587
+ setMode("typing");
588
+ }
589
+ }
590
+ };
591
+
592
+ var setEventHandlers = function () {
593
+ util.addEvent(panels.input, "keypress", function (event) {
594
+ // keyCode 89: y
595
+ // keyCode 90: z
596
+ if ((event.ctrlKey || event.metaKey) && (event.keyCode == 89 || event.keyCode == 90)) {
597
+ event.preventDefault();
598
+ }
599
+ });
600
+
601
+ var handlePaste = function () {
602
+ if (uaSniffed.isIE || (inputStateObj && inputStateObj.text != panels.input.value)) {
603
+ if (timer == undefined) {
604
+ mode = "paste";
605
+ saveState();
606
+ refreshState();
607
+ }
608
+ }
609
+ };
610
+
611
+ util.addEvent(panels.input, "keydown", handleCtrlYZ);
612
+ util.addEvent(panels.input, "keydown", handleModeChange);
613
+ util.addEvent(panels.input, "mousedown", function () {
614
+ setMode("moving");
615
+ });
616
+
617
+ panels.input.onpaste = handlePaste;
618
+ panels.input.ondrop = handlePaste;
619
+ };
620
+
621
+ var init = function () {
622
+ setEventHandlers();
623
+ refreshState(true);
624
+ saveState();
625
+ };
626
+
627
+ init();
628
+ }
629
+
630
+ // end of UndoManager
631
+
632
+ // The input textarea state/contents.
633
+ // This is used to implement undo/redo by the undo manager.
634
+ function TextareaState(panels, isInitialState) {
635
+
636
+ // Aliases
637
+ var stateObj = this;
638
+ var inputArea = panels.input;
639
+ this.init = function () {
640
+ if (!util.isVisible(inputArea)) {
641
+ return;
642
+ }
643
+ if (!isInitialState && doc.activeElement && doc.activeElement !== inputArea) { // this happens when tabbing out of the input box
644
+ return;
645
+ }
646
+
647
+ this.setInputAreaSelectionStartEnd();
648
+ this.scrollTop = inputArea.scrollTop;
649
+ if (!this.text && inputArea.selectionStart || inputArea.selectionStart === 0) {
650
+ this.text = inputArea.value;
651
+ }
652
+
653
+ }
654
+
655
+ // Sets the selected text in the input box after we've performed an
656
+ // operation.
657
+ this.setInputAreaSelection = function () {
658
+
659
+ if (!util.isVisible(inputArea)) {
660
+ return;
661
+ }
662
+
663
+ if (inputArea.selectionStart !== undefined && !uaSniffed.isOpera) {
664
+
665
+ inputArea.focus();
666
+ inputArea.selectionStart = stateObj.start;
667
+ inputArea.selectionEnd = stateObj.end;
668
+ inputArea.scrollTop = stateObj.scrollTop;
669
+ }
670
+ else if (doc.selection) {
671
+
672
+ if (doc.activeElement && doc.activeElement !== inputArea) {
673
+ return;
674
+ }
675
+
676
+ inputArea.focus();
677
+ var range = inputArea.createTextRange();
678
+ range.moveStart("character", -inputArea.value.length);
679
+ range.moveEnd("character", -inputArea.value.length);
680
+ range.moveEnd("character", stateObj.end);
681
+ range.moveStart("character", stateObj.start);
682
+ range.select();
683
+ }
684
+ };
685
+
686
+ this.setInputAreaSelectionStartEnd = function () {
687
+
688
+ if (!panels.ieCachedRange && (inputArea.selectionStart || inputArea.selectionStart === 0)) {
689
+
690
+ stateObj.start = inputArea.selectionStart;
691
+ stateObj.end = inputArea.selectionEnd;
692
+ }
693
+ else if (doc.selection) {
694
+
695
+ stateObj.text = util.fixEolChars(inputArea.value);
696
+
697
+ // IE loses the selection in the textarea when buttons are
698
+ // clicked. On IE we cache the selection. Here, if something is cached,
699
+ // we take it.
700
+ var range = panels.ieCachedRange || doc.selection.createRange();
701
+
702
+ var fixedRange = util.fixEolChars(range.text);
703
+ var marker = "\x07";
704
+ var markedRange = marker + fixedRange + marker;
705
+ range.text = markedRange;
706
+ var inputText = util.fixEolChars(inputArea.value);
707
+
708
+ range.moveStart("character", -markedRange.length);
709
+ range.text = fixedRange;
710
+
711
+ stateObj.start = inputText.indexOf(marker);
712
+ stateObj.end = inputText.lastIndexOf(marker) - marker.length;
713
+
714
+ var len = stateObj.text.length - util.fixEolChars(inputArea.value).length;
715
+
716
+ if (len) {
717
+ range.moveStart("character", -fixedRange.length);
718
+ while (len--) {
719
+ fixedRange += "\n";
720
+ stateObj.end += 1;
721
+ }
722
+ range.text = fixedRange;
723
+ }
724
+
725
+ if (panels.ieCachedRange)
726
+ stateObj.scrollTop = panels.ieCachedScrollTop; // this is set alongside with ieCachedRange
727
+
728
+ panels.ieCachedRange = null;
729
+
730
+ this.setInputAreaSelection();
731
+ }
732
+ };
733
+
734
+ // Restore this state into the input area.
735
+ this.restore = function () {
736
+
737
+ if (stateObj.text != undefined && stateObj.text != inputArea.value) {
738
+ inputArea.value = stateObj.text;
739
+ }
740
+ this.setInputAreaSelection();
741
+ inputArea.scrollTop = stateObj.scrollTop;
742
+ };
743
+
744
+ // Gets a collection of HTML chunks from the inptut textarea.
745
+ this.getChunks = function () {
746
+
747
+ var chunk = new Chunks();
748
+ chunk.before = util.fixEolChars(stateObj.text.substring(0, stateObj.start));
749
+ chunk.startTag = "";
750
+ chunk.selection = util.fixEolChars(stateObj.text.substring(stateObj.start, stateObj.end));
751
+ chunk.endTag = "";
752
+ chunk.after = util.fixEolChars(stateObj.text.substring(stateObj.end));
753
+ chunk.scrollTop = stateObj.scrollTop;
754
+
755
+ return chunk;
756
+ };
757
+
758
+ // Sets the TextareaState properties given a chunk of markdown.
759
+ this.setChunks = function (chunk) {
760
+
761
+ chunk.before = chunk.before + chunk.startTag;
762
+ chunk.after = chunk.endTag + chunk.after;
763
+
764
+ this.start = chunk.before.length;
765
+ this.end = chunk.before.length + chunk.selection.length;
766
+ this.text = chunk.before + chunk.selection + chunk.after;
767
+ this.scrollTop = chunk.scrollTop;
768
+ };
769
+ this.init();
770
+ };
771
+
772
+ function PreviewManager(converter, panels, previewRefreshCallback) {
773
+
774
+ var managerObj = this;
775
+ var timeout;
776
+ var elapsedTime;
777
+ var oldInputText;
778
+ var maxDelay = 3000;
779
+ var startType = "delayed"; // The other legal value is "manual"
780
+
781
+ // Adds event listeners to elements
782
+ var setupEvents = function (inputElem, listener) {
783
+
784
+ util.addEvent(inputElem, "input", listener);
785
+ inputElem.onpaste = listener;
786
+ inputElem.ondrop = listener;
787
+
788
+ util.addEvent(inputElem, "keypress", listener);
789
+ util.addEvent(inputElem, "keydown", listener);
790
+ };
791
+
792
+ var getDocScrollTop = function () {
793
+
794
+ var result = 0;
795
+
796
+ if (window.innerHeight) {
797
+ result = window.pageYOffset;
798
+ }
799
+ else
800
+ if (doc.documentElement && doc.documentElement.scrollTop) {
801
+ result = doc.documentElement.scrollTop;
802
+ }
803
+ else
804
+ if (doc.body) {
805
+ result = doc.body.scrollTop;
806
+ }
807
+
808
+ return result;
809
+ };
810
+
811
+ var makePreviewHtml = function () {
812
+
813
+ // If there is no registered preview panel
814
+ // there is nothing to do.
815
+ if (!panels.preview)
816
+ return;
817
+
818
+
819
+ var text = panels.input.value;
820
+ if (text && text == oldInputText) {
821
+ return; // Input text hasn't changed.
822
+ }
823
+ else {
824
+ oldInputText = text;
825
+ }
826
+
827
+ var prevTime = new Date().getTime();
828
+
829
+ text = converter.makeHtml(text);
830
+
831
+ // Calculate the processing time of the HTML creation.
832
+ // It's used as the delay time in the event listener.
833
+ var currTime = new Date().getTime();
834
+ elapsedTime = currTime - prevTime;
835
+
836
+ pushPreviewHtml(text);
837
+ };
838
+
839
+ // setTimeout is already used. Used as an event listener.
840
+ var applyTimeout = function () {
841
+
842
+ if (timeout) {
843
+ clearTimeout(timeout);
844
+ timeout = undefined;
845
+ }
846
+
847
+ if (startType !== "manual") {
848
+
849
+ var delay = 0;
850
+
851
+ if (startType === "delayed") {
852
+ delay = elapsedTime;
853
+ }
854
+
855
+ if (delay > maxDelay) {
856
+ delay = maxDelay;
857
+ }
858
+ timeout = setTimeout(makePreviewHtml, delay);
859
+ }
860
+ };
861
+
862
+ var getScaleFactor = function (panel) {
863
+ if (panel.scrollHeight <= panel.clientHeight) {
864
+ return 1;
865
+ }
866
+ return panel.scrollTop / (panel.scrollHeight - panel.clientHeight);
867
+ };
868
+
869
+ var setPanelScrollTops = function () {
870
+ if (panels.preview) {
871
+ panels.preview.scrollTop = (panels.preview.scrollHeight - panels.preview.clientHeight) * getScaleFactor(panels.preview);
872
+ }
873
+ };
874
+
875
+ this.refresh = function (requiresRefresh) {
876
+
877
+ if (requiresRefresh) {
878
+ oldInputText = "";
879
+ makePreviewHtml();
880
+ }
881
+ else {
882
+ applyTimeout();
883
+ }
884
+ };
885
+
886
+ this.processingTime = function () {
887
+ return elapsedTime;
888
+ };
889
+
890
+ var isFirstTimeFilled = true;
891
+
892
+ // IE doesn't let you use innerHTML if the element is contained somewhere in a table
893
+ // (which is the case for inline editing) -- in that case, detach the element, set the
894
+ // value, and reattach. Yes, that *is* ridiculous.
895
+ var ieSafePreviewSet = function (text) {
896
+ var preview = panels.preview;
897
+ var parent = preview.parentNode;
898
+ var sibling = preview.nextSibling;
899
+ parent.removeChild(preview);
900
+ preview.innerHTML = text;
901
+ if (!sibling)
902
+ parent.appendChild(preview);
903
+ else
904
+ parent.insertBefore(preview, sibling);
905
+ }
906
+
907
+ var nonSuckyBrowserPreviewSet = function (text) {
908
+ panels.preview.innerHTML = text;
909
+ }
910
+
911
+ var previewSetter;
912
+
913
+ var previewSet = function (text) {
914
+ if (previewSetter)
915
+ return previewSetter(text);
916
+
917
+ try {
918
+ nonSuckyBrowserPreviewSet(text);
919
+ previewSetter = nonSuckyBrowserPreviewSet;
920
+ } catch (e) {
921
+ previewSetter = ieSafePreviewSet;
922
+ previewSetter(text);
923
+ }
924
+ };
925
+
926
+ var pushPreviewHtml = function (text) {
927
+
928
+ var emptyTop = position.getTop(panels.input) - getDocScrollTop();
929
+
930
+ if (panels.preview) {
931
+ previewSet(text);
932
+ previewRefreshCallback();
933
+ }
934
+
935
+ setPanelScrollTops();
936
+
937
+ if (isFirstTimeFilled) {
938
+ isFirstTimeFilled = false;
939
+ return;
940
+ }
941
+
942
+ var fullTop = position.getTop(panels.input) - getDocScrollTop();
943
+
944
+ if (uaSniffed.isIE) {
945
+ setTimeout(function () {
946
+ window.scrollBy(0, fullTop - emptyTop);
947
+ }, 0);
948
+ }
949
+ else {
950
+ window.scrollBy(0, fullTop - emptyTop);
951
+ }
952
+ };
953
+
954
+ var init = function () {
955
+
956
+ setupEvents(panels.input, applyTimeout);
957
+ makePreviewHtml();
958
+
959
+ if (panels.preview) {
960
+ panels.preview.scrollTop = 0;
961
+ }
962
+ };
963
+
964
+ init();
965
+ };
966
+
967
+ // Creates the background behind the hyperlink text entry box.
968
+ // And download dialog
969
+ // Most of this has been moved to CSS but the div creation and
970
+ // browser-specific hacks remain here.
971
+ ui.createBackground = function () {
972
+
973
+ var background = doc.createElement("div"),
974
+ style = background.style;
975
+
976
+ background.className = "wmd-prompt-background";
977
+
978
+ style.position = "absolute";
979
+ style.top = "0";
980
+
981
+ style.zIndex = "1000";
982
+
983
+ if (uaSniffed.isIE) {
984
+ style.filter = "alpha(opacity=50)";
985
+ }
986
+ else {
987
+ style.opacity = "0.5";
988
+ }
989
+
990
+ var pageSize = position.getPageSize();
991
+ style.height = pageSize[1] + "px";
992
+
993
+ if (uaSniffed.isIE) {
994
+ style.left = doc.documentElement.scrollLeft;
995
+ style.width = doc.documentElement.clientWidth;
996
+ }
997
+ else {
998
+ style.left = "0";
999
+ style.width = "100%";
1000
+ }
1001
+
1002
+ doc.body.appendChild(background);
1003
+ return background;
1004
+ };
1005
+
1006
+ // This simulates a modal dialog box and asks for the URL when you
1007
+ // click the hyperlink or image buttons.
1008
+ //
1009
+ // text: The html for the input box.
1010
+ // defaultInputText: The default value that appears in the input box.
1011
+ // callback: The function which is executed when the prompt is dismissed, either via OK or Cancel.
1012
+ // It receives a single argument; either the entered text (if OK was chosen) or null (if Cancel
1013
+ // was chosen).
1014
+ ui.prompt = function (text, defaultInputText, callback) {
1015
+
1016
+ // These variables need to be declared at this level since they are used
1017
+ // in multiple functions.
1018
+ var dialog; // The dialog box.
1019
+ var input; // The text box where you enter the hyperlink.
1020
+
1021
+
1022
+ if (defaultInputText === undefined) {
1023
+ defaultInputText = "";
1024
+ }
1025
+
1026
+ // Used as a keydown event handler. Esc dismisses the prompt.
1027
+ // Key code 27 is ESC.
1028
+ var checkEscape = function (key) {
1029
+ var code = (key.charCode || key.keyCode);
1030
+ if (code === 27) {
1031
+ close(true);
1032
+ }
1033
+ };
1034
+
1035
+ // Dismisses the hyperlink input box.
1036
+ // isCancel is true if we don't care about the input text.
1037
+ // isCancel is false if we are going to keep the text.
1038
+ var close = function (isCancel) {
1039
+ util.removeEvent(doc.body, "keydown", checkEscape);
1040
+ var text = input.value;
1041
+
1042
+ if (isCancel) {
1043
+ text = null;
1044
+ }
1045
+ else {
1046
+ // Fixes common pasting errors.
1047
+ text = text.replace(/^http:\/\/(https?|ftp):\/\//, '$1://');
1048
+ if (!/^(?:https?|ftp):\/\//.test(text))
1049
+ text = 'http://' + text;
1050
+ }
1051
+
1052
+ dialog.parentNode.removeChild(dialog);
1053
+
1054
+ callback(text);
1055
+ return false;
1056
+ };
1057
+
1058
+
1059
+
1060
+ // Create the text input box form/window.
1061
+ var createDialog = function () {
1062
+
1063
+ // The main dialog box.
1064
+ dialog = doc.createElement("div");
1065
+ dialog.className = "wmd-prompt-dialog";
1066
+ dialog.style.padding = "10px;";
1067
+ dialog.style.position = "fixed";
1068
+ dialog.style.width = "400px";
1069
+ dialog.style.zIndex = "1001";
1070
+
1071
+ // The dialog text.
1072
+ var question = doc.createElement("div");
1073
+ question.innerHTML = text;
1074
+ question.style.padding = "5px";
1075
+ dialog.appendChild(question);
1076
+
1077
+ // The web form container for the text box and buttons.
1078
+ var form = doc.createElement("form"),
1079
+ style = form.style;
1080
+ form.onsubmit = function () { return close(false); };
1081
+ style.padding = "0";
1082
+ style.margin = "0";
1083
+ style.cssFloat = "left";
1084
+ style.width = "100%";
1085
+ style.textAlign = "center";
1086
+ style.position = "relative";
1087
+ dialog.appendChild(form);
1088
+
1089
+ // The input text box
1090
+ input = doc.createElement("input");
1091
+ input.type = "text";
1092
+ input.value = defaultInputText;
1093
+ style = input.style;
1094
+ style.display = "block";
1095
+ style.width = "80%";
1096
+ style.marginLeft = style.marginRight = "auto";
1097
+ form.appendChild(input);
1098
+
1099
+ // The ok button
1100
+ var okButton = doc.createElement("input");
1101
+ okButton.type = "button";
1102
+ okButton.onclick = function () { return close(false); };
1103
+ okButton.value = "OK";
1104
+ style = okButton.style;
1105
+ style.margin = "10px";
1106
+ style.display = "inline";
1107
+ style.width = "7em";
1108
+
1109
+
1110
+ // The cancel button
1111
+ var cancelButton = doc.createElement("input");
1112
+ cancelButton.type = "button";
1113
+ cancelButton.onclick = function () { return close(true); };
1114
+ cancelButton.value = "Cancel";
1115
+ style = cancelButton.style;
1116
+ style.margin = "10px";
1117
+ style.display = "inline";
1118
+ style.width = "7em";
1119
+
1120
+ form.appendChild(okButton);
1121
+ form.appendChild(cancelButton);
1122
+
1123
+ util.addEvent(doc.body, "keydown", checkEscape);
1124
+ dialog.style.top = "50%";
1125
+ dialog.style.left = "50%";
1126
+ dialog.style.display = "block";
1127
+ if (uaSniffed.isIE_5or6) {
1128
+ dialog.style.position = "absolute";
1129
+ dialog.style.top = doc.documentElement.scrollTop + 200 + "px";
1130
+ dialog.style.left = "50%";
1131
+ }
1132
+ doc.body.appendChild(dialog);
1133
+
1134
+ // This has to be done AFTER adding the dialog to the form if you
1135
+ // want it to be centered.
1136
+ dialog.style.marginTop = -(position.getHeight(dialog) / 2) + "px";
1137
+ dialog.style.marginLeft = -(position.getWidth(dialog) / 2) + "px";
1138
+
1139
+ };
1140
+
1141
+ // Why is this in a zero-length timeout?
1142
+ // Is it working around a browser bug?
1143
+ setTimeout(function () {
1144
+
1145
+ createDialog();
1146
+
1147
+ var defTextLen = defaultInputText.length;
1148
+ if (input.selectionStart !== undefined) {
1149
+ input.selectionStart = 0;
1150
+ input.selectionEnd = defTextLen;
1151
+ }
1152
+ else if (input.createTextRange) {
1153
+ var range = input.createTextRange();
1154
+ range.collapse(false);
1155
+ range.moveStart("character", -defTextLen);
1156
+ range.moveEnd("character", defTextLen);
1157
+ range.select();
1158
+ }
1159
+
1160
+ input.focus();
1161
+ }, 0);
1162
+ };
1163
+
1164
+ function UIManager(postfix, panels, undoManager, previewManager, commandManager, helpOptions) {
1165
+
1166
+ var inputBox = panels.input,
1167
+ buttons = {}; // buttons.undo, buttons.link, etc. The actual DOM elements.
1168
+
1169
+ makeSpritedButtonRow();
1170
+
1171
+ var keyEvent = "keydown";
1172
+ if (uaSniffed.isOpera) {
1173
+ keyEvent = "keypress";
1174
+ }
1175
+
1176
+ util.addEvent(inputBox, keyEvent, function (key) {
1177
+
1178
+ // Check to see if we have a button key and, if so execute the callback.
1179
+ if ((key.ctrlKey || key.metaKey) && !key.altKey && !key.shiftKey) {
1180
+
1181
+ var keyCode = key.charCode || key.keyCode;
1182
+ var keyCodeStr = String.fromCharCode(keyCode).toLowerCase();
1183
+
1184
+ switch (keyCodeStr) {
1185
+ case "b":
1186
+ doClick(buttons.bold);
1187
+ break;
1188
+ case "i":
1189
+ doClick(buttons.italic);
1190
+ break;
1191
+ case "l":
1192
+ doClick(buttons.link);
1193
+ break;
1194
+ case "q":
1195
+ doClick(buttons.quote);
1196
+ break;
1197
+ case "k":
1198
+ doClick(buttons.code);
1199
+ break;
1200
+ case "g":
1201
+ doClick(buttons.image);
1202
+ break;
1203
+ case "o":
1204
+ doClick(buttons.olist);
1205
+ break;
1206
+ case "u":
1207
+ doClick(buttons.ulist);
1208
+ break;
1209
+ case "h":
1210
+ doClick(buttons.heading);
1211
+ break;
1212
+ case "r":
1213
+ doClick(buttons.hr);
1214
+ break;
1215
+ case "y":
1216
+ doClick(buttons.redo);
1217
+ break;
1218
+ case "z":
1219
+ if (key.shiftKey) {
1220
+ doClick(buttons.redo);
1221
+ }
1222
+ else {
1223
+ doClick(buttons.undo);
1224
+ }
1225
+ break;
1226
+ default:
1227
+ return;
1228
+ }
1229
+
1230
+
1231
+ if (key.preventDefault) {
1232
+ key.preventDefault();
1233
+ }
1234
+
1235
+ if (window.event) {
1236
+ window.event.returnValue = false;
1237
+ }
1238
+ }
1239
+ });
1240
+
1241
+ // Auto-indent on shift-enter
1242
+ util.addEvent(inputBox, "keyup", function (key) {
1243
+ if (key.shiftKey && !key.ctrlKey && !key.metaKey) {
1244
+ var keyCode = key.charCode || key.keyCode;
1245
+ // Character 13 is Enter
1246
+ if (keyCode === 13) {
1247
+ var fakeButton = {};
1248
+ fakeButton.textOp = bindCommand("doAutoindent");
1249
+ doClick(fakeButton);
1250
+ }
1251
+ }
1252
+ });
1253
+
1254
+ // special handler because IE clears the context of the textbox on ESC
1255
+ if (uaSniffed.isIE) {
1256
+ util.addEvent(inputBox, "keydown", function (key) {
1257
+ var code = key.keyCode;
1258
+ if (code === 27) {
1259
+ return false;
1260
+ }
1261
+ });
1262
+ }
1263
+
1264
+
1265
+ // Perform the button's action.
1266
+ function doClick(button) {
1267
+
1268
+ inputBox.focus();
1269
+
1270
+ if (button.textOp) {
1271
+
1272
+ if (undoManager) {
1273
+ undoManager.setCommandMode();
1274
+ }
1275
+
1276
+ var state = new TextareaState(panels);
1277
+
1278
+ if (!state) {
1279
+ return;
1280
+ }
1281
+
1282
+ var chunks = state.getChunks();
1283
+
1284
+ // Some commands launch a "modal" prompt dialog. Javascript
1285
+ // can't really make a modal dialog box and the WMD code
1286
+ // will continue to execute while the dialog is displayed.
1287
+ // This prevents the dialog pattern I'm used to and means
1288
+ // I can't do something like this:
1289
+ //
1290
+ // var link = CreateLinkDialog();
1291
+ // makeMarkdownLink(link);
1292
+ //
1293
+ // Instead of this straightforward method of handling a
1294
+ // dialog I have to pass any code which would execute
1295
+ // after the dialog is dismissed (e.g. link creation)
1296
+ // in a function parameter.
1297
+ //
1298
+ // Yes this is awkward and I think it sucks, but there's
1299
+ // no real workaround. Only the image and link code
1300
+ // create dialogs and require the function pointers.
1301
+ var fixupInputArea = function () {
1302
+
1303
+ inputBox.focus();
1304
+
1305
+ if (chunks) {
1306
+ state.setChunks(chunks);
1307
+ }
1308
+
1309
+ state.restore();
1310
+ previewManager.refresh();
1311
+ };
1312
+
1313
+ var noCleanup = button.textOp(chunks, fixupInputArea);
1314
+
1315
+ if (!noCleanup) {
1316
+ fixupInputArea();
1317
+ }
1318
+
1319
+ }
1320
+
1321
+ if (button.execute) {
1322
+ button.execute(undoManager);
1323
+ }
1324
+ };
1325
+
1326
+ function setupButton(button, isEnabled) {
1327
+
1328
+ var normalYShift = "0px";
1329
+ var disabledYShift = "-20px";
1330
+ var highlightYShift = "-40px";
1331
+ var image = button.getElementsByTagName("span")[0];
1332
+ if (isEnabled) {
1333
+ image.style.backgroundPosition = button.XShift + " " + normalYShift;
1334
+ button.onmouseover = function () {
1335
+ image.style.backgroundPosition = this.XShift + " " + highlightYShift;
1336
+ };
1337
+
1338
+ button.onmouseout = function () {
1339
+ image.style.backgroundPosition = this.XShift + " " + normalYShift;
1340
+ };
1341
+
1342
+ // IE tries to select the background image "button" text (it's
1343
+ // implemented in a list item) so we have to cache the selection
1344
+ // on mousedown.
1345
+ if (uaSniffed.isIE) {
1346
+ button.onmousedown = function () {
1347
+ if (doc.activeElement && doc.activeElement !== panels.input) { // we're not even in the input box, so there's no selection
1348
+ return;
1349
+ }
1350
+ panels.ieCachedRange = document.selection.createRange();
1351
+ panels.ieCachedScrollTop = panels.input.scrollTop;
1352
+ };
1353
+ }
1354
+
1355
+ if (!button.isHelp) {
1356
+ button.onclick = function () {
1357
+ if (this.onmouseout) {
1358
+ this.onmouseout();
1359
+ }
1360
+ doClick(this);
1361
+ return false;
1362
+ }
1363
+ }
1364
+ }
1365
+ else {
1366
+ image.style.backgroundPosition = button.XShift + " " + disabledYShift;
1367
+ button.onmouseover = button.onmouseout = button.onclick = function () { };
1368
+ }
1369
+ }
1370
+
1371
+ function bindCommand(method) {
1372
+ if (typeof method === "string")
1373
+ method = commandManager[method];
1374
+ return function () { method.apply(commandManager, arguments); }
1375
+ }
1376
+
1377
+ function makeSpritedButtonRow() {
1378
+
1379
+ var buttonBar = panels.buttonBar;
1380
+
1381
+ var normalYShift = "0px";
1382
+ var disabledYShift = "-20px";
1383
+ var highlightYShift = "-40px";
1384
+
1385
+ var buttonRow = document.createElement("ul");
1386
+ buttonRow.id = "wmd-button-row" + postfix;
1387
+ buttonRow.className = 'wmd-button-row';
1388
+ buttonRow = buttonBar.appendChild(buttonRow);
1389
+ var xPosition = 0;
1390
+ var makeButton = function (id, title, XShift, textOp) {
1391
+ var button = document.createElement("li");
1392
+ button.className = "wmd-button";
1393
+ button.style.left = xPosition + "px";
1394
+ xPosition += 25;
1395
+ var buttonImage = document.createElement("span");
1396
+ button.id = id + postfix;
1397
+ button.appendChild(buttonImage);
1398
+ button.title = title;
1399
+ button.XShift = XShift;
1400
+ if (textOp)
1401
+ button.textOp = textOp;
1402
+ setupButton(button, true);
1403
+ buttonRow.appendChild(button);
1404
+ return button;
1405
+ };
1406
+ var makeSpacer = function (num) {
1407
+ var spacer = document.createElement("li");
1408
+ spacer.className = "wmd-spacer wmd-spacer" + num;
1409
+ spacer.id = "wmd-spacer" + num + postfix;
1410
+ buttonRow.appendChild(spacer);
1411
+ xPosition += 25;
1412
+ }
1413
+
1414
+ buttons.bold = makeButton("wmd-bold-button", "Strong <strong> Ctrl+B", "0px", bindCommand("doBold"));
1415
+ buttons.italic = makeButton("wmd-italic-button", "Emphasis <em> Ctrl+I", "-20px", bindCommand("doItalic"));
1416
+ makeSpacer(1);
1417
+ buttons.link = makeButton("wmd-link-button", "Hyperlink <a> Ctrl+L", "-40px", bindCommand(function (chunk, postProcessing) {
1418
+ return this.doLinkOrImage(chunk, postProcessing, false);
1419
+ }));
1420
+ buttons.quote = makeButton("wmd-quote-button", "Blockquote <blockquote> Ctrl+Q", "-60px", bindCommand("doBlockquote"));
1421
+ buttons.code = makeButton("wmd-code-button", "Code Sample <pre><code> Ctrl+K", "-80px", bindCommand("doCode"));
1422
+ buttons.image = makeButton("wmd-image-button", "Image <img> Ctrl+G", "-100px", bindCommand(function (chunk, postProcessing) {
1423
+ return this.doLinkOrImage(chunk, postProcessing, true);
1424
+ }));
1425
+ makeSpacer(2);
1426
+ buttons.olist = makeButton("wmd-olist-button", "Numbered List <ol> Ctrl+O", "-120px", bindCommand(function (chunk, postProcessing) {
1427
+ this.doList(chunk, postProcessing, true);
1428
+ }));
1429
+ buttons.ulist = makeButton("wmd-ulist-button", "Bulleted List <ul> Ctrl+U", "-140px", bindCommand(function (chunk, postProcessing) {
1430
+ this.doList(chunk, postProcessing, false);
1431
+ }));
1432
+ buttons.heading = makeButton("wmd-heading-button", "Heading <h1>/<h2> Ctrl+H", "-160px", bindCommand("doHeading"));
1433
+ buttons.hr = makeButton("wmd-hr-button", "Horizontal Rule <hr> Ctrl+R", "-180px", bindCommand("doHorizontalRule"));
1434
+ makeSpacer(3);
1435
+ buttons.undo = makeButton("wmd-undo-button", "Undo - Ctrl+Z", "-200px", null);
1436
+ buttons.undo.execute = function (manager) { if (manager) manager.undo(); };
1437
+
1438
+ var redoTitle = /win/.test(nav.platform.toLowerCase()) ?
1439
+ "Redo - Ctrl+Y" :
1440
+ "Redo - Ctrl+Shift+Z"; // mac and other non-Windows platforms
1441
+
1442
+ buttons.redo = makeButton("wmd-redo-button", redoTitle, "-220px", null);
1443
+ buttons.redo.execute = function (manager) { if (manager) manager.redo(); };
1444
+
1445
+ if (helpOptions) {
1446
+ var helpButton = document.createElement("li");
1447
+ var helpButtonImage = document.createElement("span");
1448
+ helpButton.appendChild(helpButtonImage);
1449
+ helpButton.className = "wmd-button wmd-help-button";
1450
+ helpButton.id = "wmd-help-button" + postfix;
1451
+ helpButton.XShift = "-240px";
1452
+ helpButton.isHelp = true;
1453
+ helpButton.style.right = "0px";
1454
+ helpButton.title = helpOptions.title || defaultHelpHoverTitle;
1455
+ helpButton.onclick = helpOptions.handler;
1456
+
1457
+ setupButton(helpButton, true);
1458
+ buttonRow.appendChild(helpButton);
1459
+ buttons.help = helpButton;
1460
+ }
1461
+
1462
+ setUndoRedoButtonStates();
1463
+ }
1464
+
1465
+ function setUndoRedoButtonStates() {
1466
+ if (undoManager) {
1467
+ setupButton(buttons.undo, undoManager.canUndo());
1468
+ setupButton(buttons.redo, undoManager.canRedo());
1469
+ }
1470
+ };
1471
+
1472
+ this.setUndoRedoButtonStates = setUndoRedoButtonStates;
1473
+
1474
+ }
1475
+
1476
+ function CommandManager(pluginHooks) {
1477
+ this.hooks = pluginHooks;
1478
+ }
1479
+
1480
+ var commandProto = CommandManager.prototype;
1481
+
1482
+ // The markdown symbols - 4 spaces = code, > = blockquote, etc.
1483
+ commandProto.prefixes = "(?:\\s{4,}|\\s*>|\\s*-\\s+|\\s*\\d+\\.|=|\\+|-|_|\\*|#|\\s*\\[[^\n]]+\\]:)";
1484
+
1485
+ // Remove markdown symbols from the chunk selection.
1486
+ commandProto.unwrap = function (chunk) {
1487
+ var txt = new re("([^\\n])\\n(?!(\\n|" + this.prefixes + "))", "g");
1488
+ chunk.selection = chunk.selection.replace(txt, "$1 $2");
1489
+ };
1490
+
1491
+ commandProto.wrap = function (chunk, len) {
1492
+ this.unwrap(chunk);
1493
+ var regex = new re("(.{1," + len + "})( +|$\\n?)", "gm"),
1494
+ that = this;
1495
+
1496
+ chunk.selection = chunk.selection.replace(regex, function (line, marked) {
1497
+ if (new re("^" + that.prefixes, "").test(line)) {
1498
+ return line;
1499
+ }
1500
+ return marked + "\n";
1501
+ });
1502
+
1503
+ chunk.selection = chunk.selection.replace(/\s+$/, "");
1504
+ };
1505
+
1506
+ commandProto.doBold = function (chunk, postProcessing) {
1507
+ return this.doBorI(chunk, postProcessing, 2, "strong text");
1508
+ };
1509
+
1510
+ commandProto.doItalic = function (chunk, postProcessing) {
1511
+ return this.doBorI(chunk, postProcessing, 1, "emphasized text");
1512
+ };
1513
+
1514
+ // chunk: The selected region that will be enclosed with */**
1515
+ // nStars: 1 for italics, 2 for bold
1516
+ // insertText: If you just click the button without highlighting text, this gets inserted
1517
+ commandProto.doBorI = function (chunk, postProcessing, nStars, insertText) {
1518
+
1519
+ // Get rid of whitespace and fixup newlines.
1520
+ chunk.trimWhitespace();
1521
+ chunk.selection = chunk.selection.replace(/\n{2,}/g, "\n");
1522
+
1523
+ // Look for stars before and after. Is the chunk already marked up?
1524
+ // note that these regex matches cannot fail
1525
+ var starsBefore = /(\**$)/.exec(chunk.before)[0];
1526
+ var starsAfter = /(^\**)/.exec(chunk.after)[0];
1527
+
1528
+ var prevStars = Math.min(starsBefore.length, starsAfter.length);
1529
+
1530
+ // Remove stars if we have to since the button acts as a toggle.
1531
+ if ((prevStars >= nStars) && (prevStars != 2 || nStars != 1)) {
1532
+ chunk.before = chunk.before.replace(re("[*]{" + nStars + "}$", ""), "");
1533
+ chunk.after = chunk.after.replace(re("^[*]{" + nStars + "}", ""), "");
1534
+ }
1535
+ else if (!chunk.selection && starsAfter) {
1536
+ // It's not really clear why this code is necessary. It just moves
1537
+ // some arbitrary stuff around.
1538
+ chunk.after = chunk.after.replace(/^([*_]*)/, "");
1539
+ chunk.before = chunk.before.replace(/(\s?)$/, "");
1540
+ var whitespace = re.$1;
1541
+ chunk.before = chunk.before + starsAfter + whitespace;
1542
+ }
1543
+ else {
1544
+
1545
+ // In most cases, if you don't have any selected text and click the button
1546
+ // you'll get a selected, marked up region with the default text inserted.
1547
+ if (!chunk.selection && !starsAfter) {
1548
+ chunk.selection = insertText;
1549
+ }
1550
+
1551
+ // Add the true markup.
1552
+ var markup = nStars <= 1 ? "*" : "**"; // shouldn't the test be = ?
1553
+ chunk.before = chunk.before + markup;
1554
+ chunk.after = markup + chunk.after;
1555
+ }
1556
+
1557
+ return;
1558
+ };
1559
+
1560
+ commandProto.stripLinkDefs = function (text, defsToAdd) {
1561
+
1562
+ text = text.replace(/^[ ]{0,3}\[(\d+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|$)/gm,
1563
+ function (totalMatch, id, link, newlines, title) {
1564
+ defsToAdd[id] = totalMatch.replace(/\s*$/, "");
1565
+ if (newlines) {
1566
+ // Strip the title and return that separately.
1567
+ defsToAdd[id] = totalMatch.replace(/["(](.+?)[")]$/, "");
1568
+ return newlines + title;
1569
+ }
1570
+ return "";
1571
+ });
1572
+
1573
+ return text;
1574
+ };
1575
+
1576
+ commandProto.addLinkDef = function (chunk, linkDef) {
1577
+
1578
+ var refNumber = 0; // The current reference number
1579
+ var defsToAdd = {}; //
1580
+ // Start with a clean slate by removing all previous link definitions.
1581
+ chunk.before = this.stripLinkDefs(chunk.before, defsToAdd);
1582
+ chunk.selection = this.stripLinkDefs(chunk.selection, defsToAdd);
1583
+ chunk.after = this.stripLinkDefs(chunk.after, defsToAdd);
1584
+
1585
+ var defs = "";
1586
+ var regex = /(\[)((?:\[[^\]]*\]|[^\[\]])*)(\][ ]?(?:\n[ ]*)?\[)(\d+)(\])/g;
1587
+
1588
+ var addDefNumber = function (def) {
1589
+ refNumber++;
1590
+ def = def.replace(/^[ ]{0,3}\[(\d+)\]:/, " [" + refNumber + "]:");
1591
+ defs += "\n" + def;
1592
+ };
1593
+
1594
+ // note that
1595
+ // a) the recursive call to getLink cannot go infinite, because by definition
1596
+ // of regex, inner is always a proper substring of wholeMatch, and
1597
+ // b) more than one level of nesting is neither supported by the regex
1598
+ // nor making a lot of sense (the only use case for nesting is a linked image)
1599
+ var getLink = function (wholeMatch, before, inner, afterInner, id, end) {
1600
+ inner = inner.replace(regex, getLink);
1601
+ if (defsToAdd[id]) {
1602
+ addDefNumber(defsToAdd[id]);
1603
+ return before + inner + afterInner + refNumber + end;
1604
+ }
1605
+ return wholeMatch;
1606
+ };
1607
+
1608
+ chunk.before = chunk.before.replace(regex, getLink);
1609
+
1610
+ if (linkDef) {
1611
+ addDefNumber(linkDef);
1612
+ }
1613
+ else {
1614
+ chunk.selection = chunk.selection.replace(regex, getLink);
1615
+ }
1616
+
1617
+ var refOut = refNumber;
1618
+
1619
+ chunk.after = chunk.after.replace(regex, getLink);
1620
+
1621
+ if (chunk.after) {
1622
+ chunk.after = chunk.after.replace(/\n*$/, "");
1623
+ }
1624
+ if (!chunk.after) {
1625
+ chunk.selection = chunk.selection.replace(/\n*$/, "");
1626
+ }
1627
+
1628
+ chunk.after += "\n\n" + defs;
1629
+
1630
+ return refOut;
1631
+ };
1632
+
1633
+ // takes the line as entered into the add link/as image dialog and makes
1634
+ // sure the URL and the optinal title are "nice".
1635
+ function properlyEncoded(linkdef) {
1636
+ return linkdef.replace(/^\s*(.*?)(?:\s+"(.+)")?\s*$/, function (wholematch, link, title) {
1637
+ link = link.replace(/\?.*$/, function (querypart) {
1638
+ return querypart.replace(/\+/g, " "); // in the query string, a plus and a space are identical
1639
+ });
1640
+ link = decodeURIComponent(link); // unencode first, to prevent double encoding
1641
+ link = encodeURI(link).replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29');
1642
+ link = link.replace(/\?.*$/, function (querypart) {
1643
+ return querypart.replace(/\+/g, "%2b"); // since we replaced plus with spaces in the query part, all pluses that now appear where originally encoded
1644
+ });
1645
+ if (title) {
1646
+ title = title.trim ? title.trim() : title.replace(/^\s*/, "").replace(/\s*$/, "");
1647
+ title = $.trim(title).replace(/"/g, "quot;").replace(/\(/g, "&#40;").replace(/\)/g, "&#41;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1648
+ }
1649
+ return title ? link + ' "' + title + '"' : link;
1650
+ });
1651
+ }
1652
+
1653
+ commandProto.doLinkOrImage = function (chunk, postProcessing, isImage) {
1654
+
1655
+ chunk.trimWhitespace();
1656
+ chunk.findTags(/\s*!?\[/, /\][ ]?(?:\n[ ]*)?(\[.*?\])?/);
1657
+ var background;
1658
+
1659
+ if (chunk.endTag.length > 1 && chunk.startTag.length > 0) {
1660
+
1661
+ chunk.startTag = chunk.startTag.replace(/!?\[/, "");
1662
+ chunk.endTag = "";
1663
+ this.addLinkDef(chunk, null);
1664
+
1665
+ }
1666
+ else {
1667
+
1668
+ // We're moving start and end tag back into the selection, since (as we're in the else block) we're not
1669
+ // *removing* a link, but *adding* one, so whatever findTags() found is now back to being part of the
1670
+ // link text. linkEnteredCallback takes care of escaping any brackets.
1671
+ chunk.selection = chunk.startTag + chunk.selection + chunk.endTag;
1672
+ chunk.startTag = chunk.endTag = "";
1673
+
1674
+ if (/\n\n/.test(chunk.selection)) {
1675
+ this.addLinkDef(chunk, null);
1676
+ return;
1677
+ }
1678
+ var that = this;
1679
+ // The function to be executed when you enter a link and press OK or Cancel.
1680
+ // Marks up the link and adds the ref.
1681
+ var linkEnteredCallback = function (link) {
1682
+
1683
+ background.parentNode.removeChild(background);
1684
+
1685
+ if (link !== null) {
1686
+ // ( $1
1687
+ // [^\\] anything that's not a backslash
1688
+ // (?:\\\\)* an even number (this includes zero) of backslashes
1689
+ // )
1690
+ // (?= followed by
1691
+ // [[\]] an opening or closing bracket
1692
+ // )
1693
+ //
1694
+ // In other words, a non-escaped bracket. These have to be escaped now to make sure they
1695
+ // don't count as the end of the link or similar.
1696
+ // Note that the actual bracket has to be a lookahead, because (in case of to subsequent brackets),
1697
+ // the bracket in one match may be the "not a backslash" character in the next match, so it
1698
+ // should not be consumed by the first match.
1699
+ // The "prepend a space and finally remove it" steps makes sure there is a "not a backslash" at the
1700
+ // start of the string, so this also works if the selection begins with a bracket. We cannot solve
1701
+ // this by anchoring with ^, because in the case that the selection starts with two brackets, this
1702
+ // would mean a zero-width match at the start. Since zero-width matches advance the string position,
1703
+ // the first bracket could then not act as the "not a backslash" for the second.
1704
+ chunk.selection = (" " + chunk.selection).replace(/([^\\](?:\\\\)*)(?=[[\]])/g, "$1\\").substr(1);
1705
+
1706
+ var linkDef = " [999]: " + properlyEncoded(link);
1707
+
1708
+ var num = that.addLinkDef(chunk, linkDef);
1709
+ chunk.startTag = isImage ? "![" : "[";
1710
+ chunk.endTag = "][" + num + "]";
1711
+
1712
+ if (!chunk.selection) {
1713
+ if (isImage) {
1714
+ chunk.selection = "enter image description here";
1715
+ }
1716
+ else {
1717
+ chunk.selection = "enter link description here";
1718
+ }
1719
+ }
1720
+ }
1721
+ postProcessing();
1722
+ };
1723
+
1724
+ background = ui.createBackground();
1725
+
1726
+ if (isImage) {
1727
+ if (!this.hooks.insertImageDialog(linkEnteredCallback))
1728
+ ui.prompt(imageDialogText, imageDefaultText, linkEnteredCallback);
1729
+ }
1730
+ else {
1731
+ ui.prompt(linkDialogText, linkDefaultText, linkEnteredCallback);
1732
+ }
1733
+ return true;
1734
+ }
1735
+ };
1736
+
1737
+ // When making a list, hitting shift-enter will put your cursor on the next line
1738
+ // at the current indent level.
1739
+ commandProto.doAutoindent = function (chunk, postProcessing) {
1740
+
1741
+ var commandMgr = this,
1742
+ fakeSelection = false;
1743
+
1744
+ chunk.before = chunk.before.replace(/(\n|^)[ ]{0,3}([*+-]|\d+[.])[ \t]*\n$/, "\n\n");
1745
+ chunk.before = chunk.before.replace(/(\n|^)[ ]{0,3}>[ \t]*\n$/, "\n\n");
1746
+ chunk.before = chunk.before.replace(/(\n|^)[ \t]+\n$/, "\n\n");
1747
+
1748
+ // There's no selection, end the cursor wasn't at the end of the line:
1749
+ // The user wants to split the current list item / code line / blockquote line
1750
+ // (for the latter it doesn't really matter) in two. Temporarily select the
1751
+ // (rest of the) line to achieve this.
1752
+ if (!chunk.selection && !/^[ \t]*(?:\n|$)/.test(chunk.after)) {
1753
+ chunk.after = chunk.after.replace(/^[^\n]*/, function (wholeMatch) {
1754
+ chunk.selection = wholeMatch;
1755
+ return "";
1756
+ });
1757
+ fakeSelection = true;
1758
+ }
1759
+
1760
+ if (/(\n|^)[ ]{0,3}([*+-]|\d+[.])[ \t]+.*\n$/.test(chunk.before)) {
1761
+ if (commandMgr.doList) {
1762
+ commandMgr.doList(chunk);
1763
+ }
1764
+ }
1765
+ if (/(\n|^)[ ]{0,3}>[ \t]+.*\n$/.test(chunk.before)) {
1766
+ if (commandMgr.doBlockquote) {
1767
+ commandMgr.doBlockquote(chunk);
1768
+ }
1769
+ }
1770
+ if (/(\n|^)(\t|[ ]{4,}).*\n$/.test(chunk.before)) {
1771
+ if (commandMgr.doCode) {
1772
+ commandMgr.doCode(chunk);
1773
+ }
1774
+ }
1775
+
1776
+ if (fakeSelection) {
1777
+ chunk.after = chunk.selection + chunk.after;
1778
+ chunk.selection = "";
1779
+ }
1780
+ };
1781
+
1782
+ commandProto.doBlockquote = function (chunk, postProcessing) {
1783
+
1784
+ chunk.selection = chunk.selection.replace(/^(\n*)([^\r]+?)(\n*)$/,
1785
+ function (totalMatch, newlinesBefore, text, newlinesAfter) {
1786
+ chunk.before += newlinesBefore;
1787
+ chunk.after = newlinesAfter + chunk.after;
1788
+ return text;
1789
+ });
1790
+
1791
+ chunk.before = chunk.before.replace(/(>[ \t]*)$/,
1792
+ function (totalMatch, blankLine) {
1793
+ chunk.selection = blankLine + chunk.selection;
1794
+ return "";
1795
+ });
1796
+
1797
+ chunk.selection = chunk.selection.replace(/^(\s|>)+$/, "");
1798
+ chunk.selection = chunk.selection || "Blockquote";
1799
+
1800
+ // The original code uses a regular expression to find out how much of the
1801
+ // text *directly before* the selection already was a blockquote:
1802
+
1803
+ /*
1804
+ if (chunk.before) {
1805
+ chunk.before = chunk.before.replace(/\n?$/, "\n");
1806
+ }
1807
+ chunk.before = chunk.before.replace(/(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*$)/,
1808
+ function (totalMatch) {
1809
+ chunk.startTag = totalMatch;
1810
+ return "";
1811
+ });
1812
+ */
1813
+
1814
+ // This comes down to:
1815
+ // Go backwards as many lines a possible, such that each line
1816
+ // a) starts with ">", or
1817
+ // b) is almost empty, except for whitespace, or
1818
+ // c) is preceeded by an unbroken chain of non-empty lines
1819
+ // leading up to a line that starts with ">" and at least one more character
1820
+ // and in addition
1821
+ // d) at least one line fulfills a)
1822
+ //
1823
+ // Since this is essentially a backwards-moving regex, it's susceptible to
1824
+ // catstrophic backtracking and can cause the browser to hang;
1825
+ // see e.g. http://meta.stackoverflow.com/questions/9807.
1826
+ //
1827
+ // Hence we replaced this by a simple state machine that just goes through the
1828
+ // lines and checks for a), b), and c).
1829
+
1830
+ var match = "",
1831
+ leftOver = "",
1832
+ line;
1833
+ if (chunk.before) {
1834
+ var lines = chunk.before.replace(/\n$/, "").split("\n");
1835
+ var inChain = false;
1836
+ for (var i = 0; i < lines.length; i++) {
1837
+ var good = false;
1838
+ line = lines[i];
1839
+ inChain = inChain && line.length > 0; // c) any non-empty line continues the chain
1840
+ if (/^>/.test(line)) { // a)
1841
+ good = true;
1842
+ if (!inChain && line.length > 1) // c) any line that starts with ">" and has at least one more character starts the chain
1843
+ inChain = true;
1844
+ } else if (/^[ \t]*$/.test(line)) { // b)
1845
+ good = true;
1846
+ } else {
1847
+ good = inChain; // c) the line is not empty and does not start with ">", so it matches if and only if we're in the chain
1848
+ }
1849
+ if (good) {
1850
+ match += line + "\n";
1851
+ } else {
1852
+ leftOver += match + line;
1853
+ match = "\n";
1854
+ }
1855
+ }
1856
+ if (!/(^|\n)>/.test(match)) { // d)
1857
+ leftOver += match;
1858
+ match = "";
1859
+ }
1860
+ }
1861
+
1862
+ chunk.startTag = match;
1863
+ chunk.before = leftOver;
1864
+
1865
+ // end of change
1866
+
1867
+ if (chunk.after) {
1868
+ chunk.after = chunk.after.replace(/^\n?/, "\n");
1869
+ }
1870
+
1871
+ chunk.after = chunk.after.replace(/^(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*)/,
1872
+ function (totalMatch) {
1873
+ chunk.endTag = totalMatch;
1874
+ return "";
1875
+ }
1876
+ );
1877
+
1878
+ var replaceBlanksInTags = function (useBracket) {
1879
+
1880
+ var replacement = useBracket ? "> " : "";
1881
+
1882
+ if (chunk.startTag) {
1883
+ chunk.startTag = chunk.startTag.replace(/\n((>|\s)*)\n$/,
1884
+ function (totalMatch, markdown) {
1885
+ return "\n" + markdown.replace(/^[ ]{0,3}>?[ \t]*$/gm, replacement) + "\n";
1886
+ });
1887
+ }
1888
+ if (chunk.endTag) {
1889
+ chunk.endTag = chunk.endTag.replace(/^\n((>|\s)*)\n/,
1890
+ function (totalMatch, markdown) {
1891
+ return "\n" + markdown.replace(/^[ ]{0,3}>?[ \t]*$/gm, replacement) + "\n";
1892
+ });
1893
+ }
1894
+ };
1895
+
1896
+ if (/^(?![ ]{0,3}>)/m.test(chunk.selection)) {
1897
+ this.wrap(chunk, SETTINGS.lineLength - 2);
1898
+ chunk.selection = chunk.selection.replace(/^/gm, "> ");
1899
+ replaceBlanksInTags(true);
1900
+ chunk.skipLines();
1901
+ } else {
1902
+ chunk.selection = chunk.selection.replace(/^[ ]{0,3}> ?/gm, "");
1903
+ this.unwrap(chunk);
1904
+ replaceBlanksInTags(false);
1905
+
1906
+ if (!/^(\n|^)[ ]{0,3}>/.test(chunk.selection) && chunk.startTag) {
1907
+ chunk.startTag = chunk.startTag.replace(/\n{0,2}$/, "\n\n");
1908
+ }
1909
+
1910
+ if (!/(\n|^)[ ]{0,3}>.*$/.test(chunk.selection) && chunk.endTag) {
1911
+ chunk.endTag = chunk.endTag.replace(/^\n{0,2}/, "\n\n");
1912
+ }
1913
+ }
1914
+
1915
+ chunk.selection = this.hooks.postBlockquoteCreation(chunk.selection);
1916
+
1917
+ if (!/\n/.test(chunk.selection)) {
1918
+ chunk.selection = chunk.selection.replace(/^(> *)/,
1919
+ function (wholeMatch, blanks) {
1920
+ chunk.startTag += blanks;
1921
+ return "";
1922
+ });
1923
+ }
1924
+ };
1925
+
1926
+ commandProto.doCode = function (chunk, postProcessing) {
1927
+
1928
+ var hasTextBefore = /\S[ ]*$/.test(chunk.before);
1929
+ var hasTextAfter = /^[ ]*\S/.test(chunk.after);
1930
+
1931
+ // Use 'four space' markdown if the selection is on its own
1932
+ // line or is multiline.
1933
+ if ((!hasTextAfter && !hasTextBefore) || /\n/.test(chunk.selection)) {
1934
+
1935
+ chunk.before = chunk.before.replace(/[ ]{4}$/,
1936
+ function (totalMatch) {
1937
+ chunk.selection = totalMatch + chunk.selection;
1938
+ return "";
1939
+ });
1940
+
1941
+ var nLinesBack = 1;
1942
+ var nLinesForward = 1;
1943
+
1944
+ if (/(\n|^)(\t|[ ]{4,}).*\n$/.test(chunk.before)) {
1945
+ nLinesBack = 0;
1946
+ }
1947
+ if (/^\n(\t|[ ]{4,})/.test(chunk.after)) {
1948
+ nLinesForward = 0;
1949
+ }
1950
+
1951
+ chunk.skipLines(nLinesBack, nLinesForward);
1952
+
1953
+ if (!chunk.selection) {
1954
+ chunk.startTag = " ";
1955
+ chunk.selection = "enter code here";
1956
+ }
1957
+ else {
1958
+ if (/^[ ]{0,3}\S/m.test(chunk.selection)) {
1959
+ if (/\n/.test(chunk.selection))
1960
+ chunk.selection = chunk.selection.replace(/^/gm, " ");
1961
+ else // if it's not multiline, do not select the four added spaces; this is more consistent with the doList behavior
1962
+ chunk.before += " ";
1963
+ }
1964
+ else {
1965
+ chunk.selection = chunk.selection.replace(/^[ ]{4}/gm, "");
1966
+ }
1967
+ }
1968
+ }
1969
+ else {
1970
+ // Use backticks (`) to delimit the code block.
1971
+
1972
+ chunk.trimWhitespace();
1973
+ chunk.findTags(/`/, /`/);
1974
+
1975
+ if (!chunk.startTag && !chunk.endTag) {
1976
+ chunk.startTag = chunk.endTag = "`";
1977
+ if (!chunk.selection) {
1978
+ chunk.selection = "enter code here";
1979
+ }
1980
+ }
1981
+ else if (chunk.endTag && !chunk.startTag) {
1982
+ chunk.before += chunk.endTag;
1983
+ chunk.endTag = "";
1984
+ }
1985
+ else {
1986
+ chunk.startTag = chunk.endTag = "";
1987
+ }
1988
+ }
1989
+ };
1990
+
1991
+ commandProto.doList = function (chunk, postProcessing, isNumberedList) {
1992
+
1993
+ // These are identical except at the very beginning and end.
1994
+ // Should probably use the regex extension function to make this clearer.
1995
+ var previousItemsRegex = /(\n|^)(([ ]{0,3}([*+-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*$/;
1996
+ var nextItemsRegex = /^\n*(([ ]{0,3}([*+-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*/;
1997
+
1998
+ // The default bullet is a dash but others are possible.
1999
+ // This has nothing to do with the particular HTML bullet,
2000
+ // it's just a markdown bullet.
2001
+ var bullet = "-";
2002
+
2003
+ // The number in a numbered list.
2004
+ var num = 1;
2005
+
2006
+ // Get the item prefix - e.g. " 1. " for a numbered list, " - " for a bulleted list.
2007
+ var getItemPrefix = function () {
2008
+ var prefix;
2009
+ if (isNumberedList) {
2010
+ prefix = " " + num + ". ";
2011
+ num++;
2012
+ }
2013
+ else {
2014
+ prefix = " " + bullet + " ";
2015
+ }
2016
+ return prefix;
2017
+ };
2018
+
2019
+ // Fixes the prefixes of the other list items.
2020
+ var getPrefixedItem = function (itemText) {
2021
+
2022
+ // The numbering flag is unset when called by autoindent.
2023
+ if (isNumberedList === undefined) {
2024
+ isNumberedList = /^\s*\d/.test(itemText);
2025
+ }
2026
+
2027
+ // Renumber/bullet the list element.
2028
+ itemText = itemText.replace(/^[ ]{0,3}([*+-]|\d+[.])\s/gm,
2029
+ function (_) {
2030
+ return getItemPrefix();
2031
+ });
2032
+
2033
+ return itemText;
2034
+ };
2035
+
2036
+ chunk.findTags(/(\n|^)*[ ]{0,3}([*+-]|\d+[.])\s+/, null);
2037
+
2038
+ if (chunk.before && !/\n$/.test(chunk.before) && !/^\n/.test(chunk.startTag)) {
2039
+ chunk.before += chunk.startTag;
2040
+ chunk.startTag = "";
2041
+ }
2042
+
2043
+ if (chunk.startTag) {
2044
+
2045
+ var hasDigits = /\d+[.]/.test(chunk.startTag);
2046
+ chunk.startTag = "";
2047
+ chunk.selection = chunk.selection.replace(/\n[ ]{4}/g, "\n");
2048
+ this.unwrap(chunk);
2049
+ chunk.skipLines();
2050
+
2051
+ if (hasDigits) {
2052
+ // Have to renumber the bullet points if this is a numbered list.
2053
+ chunk.after = chunk.after.replace(nextItemsRegex, getPrefixedItem);
2054
+ }
2055
+ if (isNumberedList == hasDigits) {
2056
+ return;
2057
+ }
2058
+ }
2059
+
2060
+ var nLinesUp = 1;
2061
+
2062
+ chunk.before = chunk.before.replace(previousItemsRegex,
2063
+ function (itemText) {
2064
+ if (/^\s*([*+-])/.test(itemText)) {
2065
+ bullet = re.$1;
2066
+ }
2067
+ nLinesUp = /[^\n]\n\n[^\n]/.test(itemText) ? 1 : 0;
2068
+ return getPrefixedItem(itemText);
2069
+ });
2070
+
2071
+ if (!chunk.selection) {
2072
+ chunk.selection = "List item";
2073
+ }
2074
+
2075
+ var prefix = getItemPrefix();
2076
+
2077
+ var nLinesDown = 1;
2078
+
2079
+ chunk.after = chunk.after.replace(nextItemsRegex,
2080
+ function (itemText) {
2081
+ nLinesDown = /[^\n]\n\n[^\n]/.test(itemText) ? 1 : 0;
2082
+ return getPrefixedItem(itemText);
2083
+ });
2084
+
2085
+ chunk.trimWhitespace(true);
2086
+ chunk.skipLines(nLinesUp, nLinesDown, true);
2087
+ chunk.startTag = prefix;
2088
+ var spaces = prefix.replace(/./g, " ");
2089
+ this.wrap(chunk, SETTINGS.lineLength - spaces.length);
2090
+ chunk.selection = chunk.selection.replace(/\n/g, "\n" + spaces);
2091
+
2092
+ };
2093
+
2094
+ commandProto.doHeading = function (chunk, postProcessing) {
2095
+
2096
+ // Remove leading/trailing whitespace and reduce internal spaces to single spaces.
2097
+ chunk.selection = chunk.selection.replace(/\s+/g, " ");
2098
+ chunk.selection = chunk.selection.replace(/(^\s+|\s+$)/g, "");
2099
+
2100
+ // If we clicked the button with no selected text, we just
2101
+ // make a level 2 hash header around some default text.
2102
+ if (!chunk.selection) {
2103
+ chunk.startTag = "## ";
2104
+ chunk.selection = "Heading";
2105
+ chunk.endTag = " ##";
2106
+ return;
2107
+ }
2108
+
2109
+ var headerLevel = 0; // The existing header level of the selected text.
2110
+
2111
+ // Remove any existing hash heading markdown and save the header level.
2112
+ chunk.findTags(/#+[ ]*/, /[ ]*#+/);
2113
+ if (/#+/.test(chunk.startTag)) {
2114
+ headerLevel = re.lastMatch.length;
2115
+ }
2116
+ chunk.startTag = chunk.endTag = "";
2117
+
2118
+ // Try to get the current header level by looking for - and = in the line
2119
+ // below the selection.
2120
+ chunk.findTags(null, /\s?(-+|=+)/);
2121
+ if (/=+/.test(chunk.endTag)) {
2122
+ headerLevel = 1;
2123
+ }
2124
+ if (/-+/.test(chunk.endTag)) {
2125
+ headerLevel = 2;
2126
+ }
2127
+
2128
+ // Skip to the next line so we can create the header markdown.
2129
+ chunk.startTag = chunk.endTag = "";
2130
+ chunk.skipLines(1, 1);
2131
+
2132
+ // We make a level 2 header if there is no current header.
2133
+ // If there is a header level, we substract one from the header level.
2134
+ // If it's already a level 1 header, it's removed.
2135
+ var headerLevelToCreate = headerLevel == 0 ? 2 : headerLevel - 1;
2136
+
2137
+ if (headerLevelToCreate > 0) {
2138
+
2139
+ // The button only creates level 1 and 2 underline headers.
2140
+ // Why not have it iterate over hash header levels? Wouldn't that be easier and cleaner?
2141
+ var headerChar = headerLevelToCreate >= 2 ? "-" : "=";
2142
+ var len = chunk.selection.length;
2143
+ if (len > SETTINGS.lineLength) {
2144
+ len = SETTINGS.lineLength;
2145
+ }
2146
+ chunk.endTag = "\n";
2147
+ while (len--) {
2148
+ chunk.endTag += headerChar;
2149
+ }
2150
+ }
2151
+ };
2152
+
2153
+ commandProto.doHorizontalRule = function (chunk, postProcessing) {
2154
+ chunk.startTag = "----------\n";
2155
+ chunk.selection = "";
2156
+ chunk.skipLines(2, 1, true);
2157
+ }
2158
+
2159
+
2160
+ })();