mirror of
https://github.com/showdownjs/showdown.git
synced 2024-03-22 13:30:55 +08:00
349 lines
8.9 KiB
JavaScript
349 lines
8.9 KiB
JavaScript
|
//
|
||
|
// 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";
|
||
|
}
|