showdown-gui.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. //
  2. // showdown-gui.js
  3. //
  4. // A sample application for Showdown, a javascript port
  5. // of Markdown.
  6. //
  7. // Copyright (c) 2007 John Fraser.
  8. //
  9. // Redistributable under a BSD-style open source license.
  10. // See license.txt for more information.
  11. //
  12. // The full source distribution is at:
  13. //
  14. // A A L
  15. // T C A
  16. // T K B
  17. //
  18. // <http://www.attacklab.net/>
  19. //
  20. //
  21. // The Showdown converter itself is in showdown.js, which must be
  22. // included by the HTML before this file is.
  23. //
  24. // showdown-gui.js assumes the id and class definitions in
  25. // showdown.html. It isn't dependent on the CSS, but it does
  26. // manually hide, display, and resize the individual panes --
  27. // overriding the stylesheets.
  28. //
  29. // This sample application only interacts with showdown.js in
  30. // two places:
  31. //
  32. // In startGui():
  33. //
  34. // converter = new Showdown.converter();
  35. //
  36. // In convertText():
  37. //
  38. // text = converter.makeHtml(text);
  39. //
  40. // The rest of this file is user interface stuff.
  41. //
  42. //
  43. // Register for onload
  44. //
  45. window.onload = startGui;
  46. //
  47. // Globals
  48. //
  49. var converter;
  50. var convertTextTimer,processingTime;
  51. var lastText,lastOutput,lastRoomLeft;
  52. var convertTextSetting, convertTextButton, paneSetting;
  53. var inputPane,previewPane,outputPane,syntaxPane;
  54. var maxDelay = 3000; // longest update pause (in ms)
  55. //
  56. // Initialization
  57. //
  58. function startGui() {
  59. // find elements
  60. convertTextSetting = document.getElementById("convertTextSetting");
  61. convertTextButton = document.getElementById("convertTextButton");
  62. paneSetting = document.getElementById("paneSetting");
  63. inputPane = document.getElementById("inputPane");
  64. previewPane = document.getElementById("previewPane");
  65. outputPane = document.getElementById("outputPane");
  66. syntaxPane = document.getElementById("syntaxPane");
  67. // set event handlers
  68. convertTextSetting.onchange = onConvertTextSettingChanged;
  69. convertTextButton.onclick = onConvertTextButtonClicked;
  70. paneSetting.onchange = onPaneSettingChanged;
  71. window.onresize = setPaneHeights;
  72. // First, try registering for keyup events
  73. // (There's no harm in calling onInput() repeatedly)
  74. window.onkeyup = inputPane.onkeyup = onInput;
  75. // In case we can't capture paste events, poll for them
  76. var pollingFallback = window.setInterval(function(){
  77. if(inputPane.value != lastText)
  78. onInput();
  79. },1000);
  80. // Try registering for paste events
  81. inputPane.onpaste = function() {
  82. // It worked! Cancel paste polling.
  83. if (pollingFallback!=undefined) {
  84. window.clearInterval(pollingFallback);
  85. pollingFallback = undefined;
  86. }
  87. onInput();
  88. }
  89. // Try registering for input events (the best solution)
  90. if (inputPane.addEventListener) {
  91. // Let's assume input also fires on paste.
  92. // No need to cancel our keyup handlers;
  93. // they're basically free.
  94. inputPane.addEventListener("input",inputPane.onpaste,false);
  95. }
  96. // poll for changes in font size
  97. // this is cheap; do it often
  98. window.setInterval(setPaneHeights,250);
  99. // start with blank page?
  100. if (top.document.location.href.match(/\?blank=1$/))
  101. inputPane.value = "";
  102. // refresh panes to avoid a hiccup
  103. onPaneSettingChanged();
  104. // build the converter
  105. converter = new Showdown.converter();
  106. // do an initial conversion to avoid a hiccup
  107. convertText();
  108. // give the input pane focus
  109. inputPane.focus();
  110. // start the other panes at the top
  111. // (our smart scrolling moved them to the bottom)
  112. previewPane.scrollTop = 0;
  113. outputPane.scrollTop = 0;
  114. }
  115. //
  116. // Conversion
  117. //
  118. function convertText() {
  119. // get input text
  120. var text = inputPane.value;
  121. // if there's no change to input, cancel conversion
  122. if (text && text == lastText) {
  123. return;
  124. } else {
  125. lastText = text;
  126. }
  127. var startTime = new Date().getTime();
  128. // Do the conversion
  129. text = converter.makeHtml(text);
  130. // display processing time
  131. var endTime = new Date().getTime();
  132. processingTime = endTime - startTime;
  133. document.getElementById("processingTime").innerHTML = processingTime+" ms";
  134. // save proportional scroll positions
  135. saveScrollPositions();
  136. // update right pane
  137. if (paneSetting.value == "outputPane") {
  138. // the output pane is selected
  139. outputPane.value = text;
  140. } else if (paneSetting.value == "previewPane") {
  141. // the preview pane is selected
  142. previewPane.innerHTML = text;
  143. }
  144. lastOutput = text;
  145. // restore proportional scroll positions
  146. restoreScrollPositions();
  147. };
  148. //
  149. // Event handlers
  150. //
  151. function onConvertTextSettingChanged() {
  152. // If the user just enabled automatic
  153. // updates, we'll do one now.
  154. onInput();
  155. }
  156. function onConvertTextButtonClicked() {
  157. // hack: force the converter to run
  158. lastText = "";
  159. convertText();
  160. inputPane.focus();
  161. }
  162. function onPaneSettingChanged() {
  163. previewPane.style.display = "none";
  164. outputPane.style.display = "none";
  165. syntaxPane.style.display = "none";
  166. // now make the selected one visible
  167. top[paneSetting.value].style.display = "block";
  168. lastRoomLeft = 0; // hack: force resize of new pane
  169. setPaneHeights();
  170. if (paneSetting.value == "outputPane") {
  171. // Update output pane
  172. outputPane.value = lastOutput;
  173. } else if (paneSetting.value == "previewPane") {
  174. // Update preview pane
  175. previewPane.innerHTML = lastOutput;
  176. }
  177. }
  178. function onInput() {
  179. // In "delayed" mode, we do the conversion at pauses in input.
  180. // The pause is equal to the last runtime, so that slow
  181. // updates happen less frequently.
  182. //
  183. // Use a timer to schedule updates. Each keystroke
  184. // resets the timer.
  185. // if we already have convertText scheduled, cancel it
  186. if (convertTextTimer) {
  187. window.clearTimeout(convertTextTimer);
  188. convertTextTimer = undefined;
  189. }
  190. if (convertTextSetting.value != "manual") {
  191. var timeUntilConvertText = 0;
  192. if (convertTextSetting.value == "delayed") {
  193. // make timer adaptive
  194. timeUntilConvertText = processingTime;
  195. }
  196. if (timeUntilConvertText > maxDelay)
  197. timeUntilConvertText = maxDelay;
  198. // Schedule convertText().
  199. // Even if we're updating every keystroke, use a timer at 0.
  200. // This gives the browser time to handle other events.
  201. convertTextTimer = window.setTimeout(convertText,timeUntilConvertText);
  202. }
  203. }
  204. //
  205. // Smart scrollbar adjustment
  206. //
  207. // We need to make sure the user can't type off the bottom
  208. // of the preview and output pages. We'll do this by saving
  209. // the proportional scroll positions before the update, and
  210. // restoring them afterwards.
  211. //
  212. var previewScrollPos;
  213. var outputScrollPos;
  214. function getScrollPos(element) {
  215. // favor the bottom when the text first overflows the window
  216. if (element.scrollHeight <= element.clientHeight)
  217. return 1.0;
  218. return element.scrollTop/(element.scrollHeight-element.clientHeight);
  219. }
  220. function setScrollPos(element,pos) {
  221. element.scrollTop = (element.scrollHeight - element.clientHeight) * pos;
  222. }
  223. function saveScrollPositions() {
  224. previewScrollPos = getScrollPos(previewPane);
  225. outputScrollPos = getScrollPos(outputPane);
  226. }
  227. function restoreScrollPositions() {
  228. // hack for IE: setting scrollTop ensures scrollHeight
  229. // has been updated after a change in contents
  230. previewPane.scrollTop = previewPane.scrollTop;
  231. setScrollPos(previewPane,previewScrollPos);
  232. setScrollPos(outputPane,outputScrollPos);
  233. }
  234. //
  235. // Textarea resizing
  236. //
  237. // Some browsers (i.e. IE) refuse to set textarea
  238. // percentage heights in standards mode. (But other units?
  239. // No problem. Percentage widths? No problem.)
  240. //
  241. // So we'll do it in javascript. If IE's behavior ever
  242. // changes, we should remove this crap and do 100% textarea
  243. // heights in CSS, because it makes resizing much smoother
  244. // on other browsers.
  245. //
  246. function getTop(element) {
  247. var sum = element.offsetTop;
  248. while(element = element.offsetParent)
  249. sum += element.offsetTop;
  250. return sum;
  251. }
  252. function getElementHeight(element) {
  253. var height = element.clientHeight;
  254. if (!height) height = element.scrollHeight;
  255. return height;
  256. }
  257. function getWindowHeight(element) {
  258. if (window.innerHeight)
  259. return window.innerHeight;
  260. else if (document.documentElement && document.documentElement.clientHeight)
  261. return document.documentElement.clientHeight;
  262. else if (document.body)
  263. return document.body.clientHeight;
  264. }
  265. function setPaneHeights() {
  266. var textarea = inputPane;
  267. var footer = document.getElementById("footer");
  268. var windowHeight = getWindowHeight();
  269. var footerHeight = getElementHeight(footer);
  270. var textareaTop = getTop(textarea);
  271. // figure out how much room the panes should fill
  272. var roomLeft = windowHeight - footerHeight - textareaTop;
  273. if (roomLeft < 0) roomLeft = 0;
  274. // if it hasn't changed, return
  275. if (roomLeft == lastRoomLeft) {
  276. return;
  277. }
  278. lastRoomLeft = roomLeft;
  279. // resize all panes
  280. inputPane.style.height = roomLeft + "px";
  281. previewPane.style.height = roomLeft + "px";
  282. outputPane.style.height = roomLeft + "px";
  283. syntaxPane.style.height = roomLeft + "px";
  284. }