123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349 |
- //
- // showdown-gui.js
- //
- // A sample application for Showdown, a javascript port
- // of Markdown.
- //
- // Copyright (c) 2007 John Fraser.
- //
- // Redistributable under a BSD-style open source license.
- // See license.txt for more information.
- //
- // The full source distribution is at:
- //
- // A A L
- // T C A
- // T K B
- //
- // <http://www.attacklab.net/>
- //
- //
- // The Showdown converter itself is in showdown.js, which must be
- // included by the HTML before this file is.
- //
- // showdown-gui.js assumes the id and class definitions in
- // showdown.html. It isn't dependent on the CSS, but it does
- // manually hide, display, and resize the individual panes --
- // overriding the stylesheets.
- //
- // This sample application only interacts with showdown.js in
- // two places:
- //
- // In startGui():
- //
- // converter = new Showdown.converter();
- //
- // In convertText():
- //
- // text = converter.makeHtml(text);
- //
- // The rest of this file is user interface stuff.
- //
- //
- // Register for onload
- //
- window.onload = startGui;
- //
- // Globals
- //
- var converter;
- var convertTextTimer,processingTime;
- var lastText,lastOutput,lastRoomLeft;
- var convertTextSetting, convertTextButton, paneSetting;
- var inputPane,previewPane,outputPane,syntaxPane;
- var maxDelay = 3000; // longest update pause (in ms)
- //
- // Initialization
- //
- function startGui() {
- // find elements
- convertTextSetting = document.getElementById("convertTextSetting");
- convertTextButton = document.getElementById("convertTextButton");
- paneSetting = document.getElementById("paneSetting");
- inputPane = document.getElementById("inputPane");
- previewPane = document.getElementById("previewPane");
- outputPane = document.getElementById("outputPane");
- syntaxPane = document.getElementById("syntaxPane");
- // set event handlers
- convertTextSetting.onchange = onConvertTextSettingChanged;
- convertTextButton.onclick = onConvertTextButtonClicked;
- paneSetting.onchange = onPaneSettingChanged;
- window.onresize = setPaneHeights;
- // First, try registering for keyup events
- // (There's no harm in calling onInput() repeatedly)
- window.onkeyup = inputPane.onkeyup = onInput;
- // In case we can't capture paste events, poll for them
- var pollingFallback = window.setInterval(function(){
- if(inputPane.value != lastText)
- onInput();
- },1000);
- // Try registering for paste events
- inputPane.onpaste = function() {
- // It worked! Cancel paste polling.
- if (pollingFallback!=undefined) {
- window.clearInterval(pollingFallback);
- pollingFallback = undefined;
- }
- onInput();
- }
- // Try registering for input events (the best solution)
- if (inputPane.addEventListener) {
- // Let's assume input also fires on paste.
- // No need to cancel our keyup handlers;
- // they're basically free.
- inputPane.addEventListener("input",inputPane.onpaste,false);
- }
- // poll for changes in font size
- // this is cheap; do it often
- window.setInterval(setPaneHeights,250);
- // start with blank page?
- if (top.document.location.href.match(/\?blank=1$/))
- inputPane.value = "";
- // refresh panes to avoid a hiccup
- onPaneSettingChanged();
- // build the converter
- converter = new Showdown.converter();
- // do an initial conversion to avoid a hiccup
- convertText();
- // give the input pane focus
- inputPane.focus();
- // start the other panes at the top
- // (our smart scrolling moved them to the bottom)
- previewPane.scrollTop = 0;
- outputPane.scrollTop = 0;
- }
- //
- // Conversion
- //
- function convertText() {
- // get input text
- var text = inputPane.value;
-
- // if there's no change to input, cancel conversion
- if (text && text == lastText) {
- return;
- } else {
- lastText = text;
- }
- var startTime = new Date().getTime();
- // Do the conversion
- text = converter.makeHtml(text);
- // display processing time
- var endTime = new Date().getTime();
- processingTime = endTime - startTime;
- document.getElementById("processingTime").innerHTML = processingTime+" ms";
- // save proportional scroll positions
- saveScrollPositions();
- // update right pane
- if (paneSetting.value == "outputPane") {
- // the output pane is selected
- outputPane.value = text;
- } else if (paneSetting.value == "previewPane") {
- // the preview pane is selected
- previewPane.innerHTML = text;
- }
- lastOutput = text;
- // restore proportional scroll positions
- restoreScrollPositions();
- };
- //
- // Event handlers
- //
- function onConvertTextSettingChanged() {
- // If the user just enabled automatic
- // updates, we'll do one now.
- onInput();
- }
- function onConvertTextButtonClicked() {
- // hack: force the converter to run
- lastText = "";
- convertText();
- inputPane.focus();
- }
- function onPaneSettingChanged() {
- previewPane.style.display = "none";
- outputPane.style.display = "none";
- syntaxPane.style.display = "none";
- // now make the selected one visible
- top[paneSetting.value].style.display = "block";
- lastRoomLeft = 0; // hack: force resize of new pane
- setPaneHeights();
- if (paneSetting.value == "outputPane") {
- // Update output pane
- outputPane.value = lastOutput;
- } else if (paneSetting.value == "previewPane") {
- // Update preview pane
- previewPane.innerHTML = lastOutput;
- }
- }
- function onInput() {
- // In "delayed" mode, we do the conversion at pauses in input.
- // The pause is equal to the last runtime, so that slow
- // updates happen less frequently.
- //
- // Use a timer to schedule updates. Each keystroke
- // resets the timer.
- // if we already have convertText scheduled, cancel it
- if (convertTextTimer) {
- window.clearTimeout(convertTextTimer);
- convertTextTimer = undefined;
- }
- if (convertTextSetting.value != "manual") {
- var timeUntilConvertText = 0;
- if (convertTextSetting.value == "delayed") {
- // make timer adaptive
- timeUntilConvertText = processingTime;
- }
- if (timeUntilConvertText > maxDelay)
- timeUntilConvertText = maxDelay;
- // Schedule convertText().
- // Even if we're updating every keystroke, use a timer at 0.
- // This gives the browser time to handle other events.
- convertTextTimer = window.setTimeout(convertText,timeUntilConvertText);
- }
- }
- //
- // Smart scrollbar adjustment
- //
- // We need to make sure the user can't type off the bottom
- // of the preview and output pages. We'll do this by saving
- // the proportional scroll positions before the update, and
- // restoring them afterwards.
- //
- var previewScrollPos;
- var outputScrollPos;
- function getScrollPos(element) {
- // favor the bottom when the text first overflows the window
- if (element.scrollHeight <= element.clientHeight)
- return 1.0;
- return element.scrollTop/(element.scrollHeight-element.clientHeight);
- }
- function setScrollPos(element,pos) {
- element.scrollTop = (element.scrollHeight - element.clientHeight) * pos;
- }
- function saveScrollPositions() {
- previewScrollPos = getScrollPos(previewPane);
- outputScrollPos = getScrollPos(outputPane);
- }
- function restoreScrollPositions() {
- // hack for IE: setting scrollTop ensures scrollHeight
- // has been updated after a change in contents
- previewPane.scrollTop = previewPane.scrollTop;
- setScrollPos(previewPane,previewScrollPos);
- setScrollPos(outputPane,outputScrollPos);
- }
- //
- // Textarea resizing
- //
- // Some browsers (i.e. IE) refuse to set textarea
- // percentage heights in standards mode. (But other units?
- // No problem. Percentage widths? No problem.)
- //
- // So we'll do it in javascript. If IE's behavior ever
- // changes, we should remove this crap and do 100% textarea
- // heights in CSS, because it makes resizing much smoother
- // on other browsers.
- //
- function getTop(element) {
- var sum = element.offsetTop;
- while(element = element.offsetParent)
- sum += element.offsetTop;
- return sum;
- }
- function getElementHeight(element) {
- var height = element.clientHeight;
- if (!height) height = element.scrollHeight;
- return height;
- }
- function getWindowHeight(element) {
- if (window.innerHeight)
- return window.innerHeight;
- else if (document.documentElement && document.documentElement.clientHeight)
- return document.documentElement.clientHeight;
- else if (document.body)
- return document.body.clientHeight;
- }
- function setPaneHeights() {
- var textarea = inputPane;
- var footer = document.getElementById("footer");
- var windowHeight = getWindowHeight();
- var footerHeight = getElementHeight(footer);
- var textareaTop = getTop(textarea);
- // figure out how much room the panes should fill
- var roomLeft = windowHeight - footerHeight - textareaTop;
- if (roomLeft < 0) roomLeft = 0;
- // if it hasn't changed, return
- if (roomLeft == lastRoomLeft) {
- return;
- }
- lastRoomLeft = roomLeft;
- // resize all panes
- inputPane.style.height = roomLeft + "px";
- previewPane.style.height = roomLeft + "px";
- outputPane.style.height = roomLeft + "px";
- syntaxPane.style.height = roomLeft + "px";
- }
|