Website openantrag.de
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
OpenAntrag/OpenAntrag/Scripts/MarkdownDeepEditor.js

1398 lines
38 KiB

//
//! MarkdownDeep - http://www.toptensoftware.com/markdowndeep
//! Copyright (C) 2010-2011 Topten Software
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this product except in
// compliance with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed under the License is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and limitations under the License.
//
/*
Usage:
// 1. Create the editor an bind to a text area, output div and an optional source view div
- text area: the text area that user types to.
- output div: a div where the transformed html will be displayed
- source view view: an optional div where a "source" view of the rendered html will be placed
var editor=new MarkdownDeepEditor.Editor(textarea_element, output_div, source_div)
// 2. Optionally set options
editor.disableShortCutKeys=true; // Disable Ctrl+B, Ctrl+I etc...
editor.disableAutoIndent=true; // Disable auto indent on enter key
editor.disableTabHandling=true; // Disable tab/shift+tab for indent
// 3. Optionally install hooks
editor.onPreTransform=function(editor, markdown) {}
editor.onPostTransform=function(editor, html) {}
editor.onPostUpdateDom=function(editor) {}
// 4. Optionally create a toolbar/UI that calls editor.InvokeCommand(cmd) where cmd is one of:
- "undo",
- "redo",
- "bold",
- "italic",
- "heading",
- "code",
- "ullist",
- "ollist",
- "indent",
- "outdent",
- "link",
- "img",
- "hr",
- "h0",
- "h1",
- "h2",
- "h3",
- "h4",
- "h5",
- "h6"
eg: editor.InvokeCommand("heading") to toggle heading style of selection
*/
var MarkdownDeepEditor=new function(){
// private:priv.
// private:.m_*
// private:.m_listType
// private:.m_prefixLen
var ie=false;
// Various keycodes
var keycode_tab = 9;
var keycode_enter = 13;
var keycode_pgup = 33;
var keycode_pgdn = 34;
var keycode_home = 36;
var keycode_end = 35;
var keycode_left = 37;
var keycode_right = 39;
var keycode_up = 38;
var keycode_down = 40;
var keycode_backspace = 8;
var keycode_delete = 46;
// Undo modes for the undo stack
var undomode_unknown = 0;
var undomode_text = 1;
var undomode_erase = 2;
var undomode_navigate = 3;
var undomode_whitespace = 4;
// Shortcut keys Ctrl+key
var shortcut_keys={
"Z": "undo",
"Y": "redo",
"B": "bold",
"I": "italic",
"H": "heading",
"K": "code",
"U": "ullist",
"O": "ollist",
"Q": "indent",
"E": "outdent",
"L": "link",
"G": "img",
"R": "hr",
"0": "h0",
"1": "h1",
"2": "h2",
"3": "h3",
"4": "h4",
"5": "h5",
"6": "h6"
}
function starts_with(str, match)
{
return str.substr(0, match.length)==match;
}
function ends_with(str, match)
{
return str.substr(-match.length)==match;
}
function is_whitespace(ch)
{
return (ch==' ' || ch=='\t' || ch=='\r' || ch=='\n');
}
function is_crlf(ch)
{
return (ch=='\r' || ch=='\n');
}
function trim(str)
{
var i=0;
var l=str.length;
while (i<l && is_whitespace(str.charAt(i)))
i++;
while (l-1>i && is_whitespace(str.charAt(l-1)))
l--;
return str.substr(i, l-i);
}
// Helper for binding events
function BindEvent(obj, event, handler)
{
if (obj.addEventListener)
{
obj.addEventListener(event, handler, false);
}
else if (obj.attachEvent)
{
obj.attachEvent("on"+event, handler);
}
}
// Helper for unbinding events
function UnbindEvent(obj, event, handler)
{
if (obj.removeEventListener)
{
obj.removeEventListener(event, handler, false);
}
else if (obj.detachEvent)
{
obj.detachEvent("on"+event, handler);
}
}
function PreventEventDefault(event)
{
if (event.preventDefault)
{
event.preventDefault();
}
if (event.cancelBubble!==undefined)
{
event.cancelBubble=true;
event.keyCode=0;
event.returnValue=false;
}
return false;
}
function offsetToRangeCharacterMove(el, offset)
{
return offset - (el.value.slice(0, offset).split("\r\n").length - 1);
}
// EditorState represents the initial and final state of an edit
function EditorState()
{
}
priv=EditorState.prototype;
priv.InitFromTextArea=function(textarea)
{
this.m_textarea=textarea;
if (ie)
{
var sel=document.selection.createRange();
var temp=sel.duplicate();
temp.moveToElementText(textarea);
var basepos=-temp.moveStart('character', -10000000);
this.m_selectionStart = -sel.moveStart('character', -10000000)-basepos;
this.m_selectionEnd = -sel.moveEnd('character', -10000000)-basepos;
this.m_text=textarea.value.replace(/\r\n/gm,"\n");
}
else
{
this.m_selectionStart = textarea.selectionStart;
this.m_selectionEnd = textarea.selectionEnd;
this.m_text=textarea.value;
}
}
priv.Duplicate=function()
{
var other=new EditorState();
other.m_textarea=this.m_textarea;
other.m_selectionEnd=this.m_selectionEnd;
other.m_selectionStart=this.m_selectionStart;
other.m_text=this.m_text;
return other;
}
priv.Apply=function()
{
if (ie)
{
this.m_textarea.value=this.m_text;
this.m_textarea.focus();
var r=this.m_textarea.createTextRange();
r.collapse(true);
r.moveEnd("character", this.m_selectionEnd);
r.moveStart("character", this.m_selectionStart);
r.select();
}
else
{
// Set the new text
var scrollTop=this.m_textarea.scrollTop;
this.m_textarea.value=this.m_text;
this.m_textarea.focus();
this.m_textarea.setSelectionRange(this.m_selectionStart, this.m_selectionEnd);
this.m_textarea.scrollTop=scrollTop;
}
}
priv.ReplaceSelection=function(str)
{
this.m_text=this.m_text.substr(0, this.m_selectionStart) + str + this.m_text.substr(this.m_selectionEnd);
this.m_selectionEnd=this.m_selectionStart + str.length;
}
function adjust_pos(pos2, editpos, del, ins)
{
if (pos2<editpos)
return pos2;
return pos2<editpos+del ? editpos : pos2 + ins - del;
}
priv.ReplaceAt=function(pos, len, str)
{
this.m_text=this.m_text.substr(0, pos) + str + this.m_text.substr(pos+len);
this.m_selectionStart=adjust_pos(this.m_selectionStart, pos, len, str.length);
this.m_selectionEnd=adjust_pos(this.m_selectionEnd, pos, len, str.length);
}
priv.getSelectedText=function()
{
return this.m_text.substr(this.m_selectionStart, this.m_selectionEnd-this.m_selectionStart);
}
priv.InflateSelection=function(ds, de)
{
this.m_selectionEnd+=de;
this.m_selectionStart-=ds;
}
priv.PreceededBy=function(str)
{
return this.m_selectionStart >= str.length && this.m_text.substr(this.m_selectionStart-str.length, str.length)==str;
}
priv.FollowedBy=function(str)
{
return this.m_text.substr(this.m_selectionEnd, str.length)==str;
}
priv.TrimSelection=function()
{
while (is_whitespace(this.m_text.charAt(this.m_selectionStart)))
this.m_selectionStart++;
while (this.m_selectionEnd>this.m_selectionStart && is_whitespace(this.m_text.charAt(this.m_selectionEnd-1)))
this.m_selectionEnd--;
}
priv.IsStartOfLine=function(pos)
{
return pos==0 || is_crlf(this.m_text.charAt(pos-1));
}
priv.FindStartOfLine=function(pos)
{
// Move start of selection back to line start
while (pos>0 && !is_crlf(this.m_text.charAt(pos-1)))
{
pos--;
}
return pos;
}
priv.FindEndOfLine=function(pos)
{
while (pos<this.m_text.length && !is_crlf(this.m_text.charAt(pos)))
{
pos++;
}
return pos;
}
priv.FindNextLine=function(pos)
{
return this.SkipEol(this.FindEndOfLine(pos));
}
priv.SkipWhiteSpace=function(pos)
{
while (pos<this.m_text.length && is_whitespace(this.m_text.charAt(pos)))
pos++;
return pos;
}
priv.SkipEol=function(pos)
{
if (this.m_text.substr(pos, 2)=="\r\n")
return pos+2;
if (is_crlf(this.m_text.charAt(pos)))
return pos+1;
return pos;
}
priv.SkipPreceedingEol=function(pos)
{
if (pos>2 && this.m_text.substr(pos-2, 2)=="\r\n")
return pos-2;
if (pos>1 && is_crlf(this.m_text.charAt(pos-1)))
return pos-1;
return pos;
}
priv.SelectWholeLines=function()
{
// Move selection to start of line
this.m_selectionStart=this.FindStartOfLine(this.m_selectionStart);
// Move end of selection to start of the next line
if (!this.IsStartOfLine(this.m_selectionEnd))
{
this.m_selectionEnd=this.SkipEol(this.FindEndOfLine(this.m_selectionEnd));
}
}
priv.SkipPreceedingWhiteSpace=function(pos)
{
while (pos>0 && is_whitespace(this.m_text.charAt(pos-1)))
{
pos--;
}
return pos;
}
priv.SkipFollowingWhiteSpace=function(pos)
{
while (is_whitespace(this.m_text.charAt(pos)))
{
pos++;
}
return pos;
}
priv.SelectSurroundingWhiteSpace=function()
{
this.m_selectionStart=this.SkipPreceedingWhiteSpace(this.m_selectionStart);
this.m_selectionEnd=this.SkipFollowingWhiteSpace(this.m_selectionEnd);
}
priv.CheckSimpleSelection=function()
{
var text=this.getSelectedText();
var m=text.match(/\n[ \t\r]*\n/);
if (m)
{
alertEx("Bitte mache eine Auswahl, die keinen Zeilenumbruch einschlie<EFBFBD>t", "warning");
return false;
}
return true;
}
// Check if line is completely blank
priv.IsBlankLine=function(p)
{
var len=this.m_text.length;
for (var i=p; i<len; i++)
{
var ch=this.m_text[i];
if (is_crlf(ch))
return true;
if (!is_whitespace(this.m_text.charAt(i)))
return false;
}
return true;
}
priv.FindStartOfParagraph=function(pos)
{
var savepos=pos;
// Move to start of first line
pos=this.FindStartOfLine(pos);
if (this.IsBlankLine(pos))
return pos;
// Move to first line after blank line
while (pos>0)
{
var p=this.FindStartOfLine(this.SkipPreceedingEol(pos));
if (p==0)
break;
if (this.IsBlankLine(p))
break;
pos=p;
}
// Is it a list?
if (this.DetectListType(pos).m_prefixLen!=0)
{
// Do it again, but stop at line with list prefix
pos=this.FindStartOfLine(savepos);
// Move to first line after blank line
while (pos>0)
{
if (this.DetectListType(pos).m_prefixLen!=0)
return pos;
// go to line before
pos=this.FindStartOfLine(this.SkipPreceedingEol(pos));
}
}
return pos;
}
priv.FindEndOfParagraph=function(pos)
{
// Skip all lines that aren't blank
while (pos<this.m_text.length)
{
if (this.IsBlankLine(pos))
break;
pos=this.FindNextLine(pos);
}
return pos;
}
// Select the paragraph
priv.SelectParagraph=function()
{
this.m_selectionStart=this.FindStartOfParagraph(this.m_selectionStart);
this.m_selectionEnd=this.FindEndOfParagraph(this.m_selectionStart);
}
// Starting at position pos, return the list type
// returns { m_listType, m_prefixLen }
priv.DetectListType=function(pos)
{
var prefix=this.m_text.substr(pos, 10);
var m=prefix.match(/^\s{0,3}(\*|\d+\.)(?:\ |\t)*/);
if (!m)
return {m_listType:"", m_prefixLen:0};
if (m[1]=='*')
return {m_listType:"*", m_prefixLen:m[0].length};
else
return {m_listType:"1", m_prefixLen:m[0].length};
}
// Editor
function Editor(textarea, div_html)
{
// Is it IE?
if (!textarea.setSelectionRange)
{
ie=true;
}
// Initialize
this.m_lastContent=null;
this.m_undoStack=[];
this.m_undoPos=0;
this.m_undoMode=undomode_navigate;
this.Markdown=new MarkdownDeep.Markdown();
this.Markdown.SafeMode=false;
this.Markdown.ExtraMode=true;
this.Markdown.NewWindowForLocalLinks=true;
this.Markdown.NewWindowForExternalLinks=true;
// Store DOM elements
this.m_textarea=textarea;
this.m_divHtml=div_html;
// Bind events
var ed=this;
BindEvent(textarea, "keyup", function(){ed.onMarkdownChanged();});
BindEvent(textarea, "keydown", function(e){return ed.onKeyDown(e);});
BindEvent(textarea, "paste", function(){ed.onMarkdownChanged();});
BindEvent(textarea, "input", function(){ed.onMarkdownChanged();});
BindEvent(textarea, "mousedown", function(){ed.SetUndoMode(undomode_navigate);});
// Do initial update
this.onMarkdownChanged();
}
var priv=Editor.prototype;
var pub=Editor.prototype;
priv.onKeyDown=function(e)
{
var newMode=null;
var retv=true;
// Normal keys only
if(e.ctrlKey || e.metaKey)
{
var key=String.fromCharCode(e.charCode||e.keyCode);
// Built in short cut key?
if (!this.disableShortCutKeys && shortcut_keys[key]!=undefined)
{
this.InvokeCommand(shortcut_keys[key]);
return PreventEventDefault(e);
}
// Standard keys
switch (key)
{
case "V": // Paste
newMode=undomode_text;
break;
case "X": // Cut
newMode=undomode_erase;
break;
}
}
else
{
switch (e.keyCode)
{
case keycode_tab:
if (!this.disableTabHandling)
{
this.InvokeCommand(e.shiftKey ? "untab" : "tab");
return PreventEventDefault(e);
}
else
{
newMode=undomode_text;
}
break;
case keycode_left:
case keycode_right:
case keycode_up:
case keycode_down:
case keycode_home:
case keycode_end:
case keycode_pgup:
case keycode_pgdn:
// Navigation mode
newMode=undomode_navigate;
break;
case keycode_backspace:
case keycode_delete:
// Delete mode
newMode=undomode_erase;
break;
case keycode_enter:
// New lines mode
newMode=undomode_whitespace;
break;
default:
// Text mode
newMode=undomode_text;
}
}
if (newMode!=null)
this.SetUndoMode(newMode);
// Special handling for enter key
if (!this.disableAutoIndent)
{
if (e.keyCode==keycode_enter && (!ie || e.ctrlKey))
{
this.IndentNewLine();
}
}
}
priv.SetUndoMode=function(newMode)
{
// Same mode?
if (this.m_undoMode==newMode)
return;
// Enter new mode, after capturing current state
this.m_undoMode=newMode;
// Capture undo state
this.CaptureUndoState();
}
priv.CaptureUndoState=function()
{
// Store a copy on the undo stack
var state=new EditorState();
state.InitFromTextArea(this.m_textarea);
this.m_undoStack.splice(this.m_undoPos, this.m_undoStack.length-this.m_undoPos, state);
this.m_undoPos=this.m_undoStack.length;
}
priv.onMarkdownChanged=function(bCreateUndoUnit)
{
// Get the markdown, see if it's changed
var new_content=this.m_textarea.value;
if (new_content===this.m_lastContent && this.m_lastContent!==null)
return;
// Call pre hook
if (this.onPreTransform)
this.onPreTransform(this, new_content);
// Transform
var output=this.Markdown.Transform(new_content);
// Call post hook
if (this.onPostTransform)
this.onPostTransform(this, output);
// Update the DOM
if (this.m_divHtml)
this.m_divHtml.innerHTML=output;
/*
if (this.m_divSource)
{
this.m_divSource.innerHTML="";
this.m_divSource.appendChild(document.createTextNode(output));
}
*/
// Call post update dom handler
if (this.onPostUpdateDom)
this.onPostUpdateDom(this);
// Save previous content
this.m_lastContent=new_content;
}
// Public method, should be called by client code if any of the MarkdownDeep
// transform options have changed
pub.onOptionsChanged=function()
{
this.m_lastContent=null;
this.onMarkdownChanged();
}
pub.cmd_undo=function()
{
if (this.m_undoPos > 0)
{
// Capture current state at end of undo buffer.
if (this.m_undoPos==this.m_undoStack.length)
{
this.CaptureUndoState();
this.m_undoPos--;
}
this.m_undoPos--;
this.m_undoStack[this.m_undoPos].Apply();
this.m_undoMode=undomode_unknown;
// Update markdown rendering
this.onMarkdownChanged();
}
}
pub.cmd_redo=function()
{
if (this.m_undoPos+1 < this.m_undoStack.length)
{
this.m_undoPos++;
this.m_undoStack[this.m_undoPos].Apply();
this.m_undoMode=undomode_unknown;
// Update markdown rendering
this.onMarkdownChanged();
// We're back at the current state
if (this.m_undoPos==this.m_undoStack.length-1)
{
this.m_undoStack.pop();
}
}
}
priv.setHeadingLevel=function(state, headingLevel)
{
// Select the entire heading
state.SelectParagraph();
state.SelectSurroundingWhiteSpace();
// Get the selected text
var text=state.getSelectedText();
// Trim all whitespace
text=trim(text);
var currentHeadingLevel=0;
var m=text.match(/^(\#+)(.*?)(\#+)?$/);
if (m)
{
text=trim(m[2]);
currentHeadingLevel=m[1].length;
}
else
{
m=text.match(/^(.*?)(?:\r\n|\n|\r)\s*(\-*|\=*)$/);
if (m)
{
text=trim(m[1]);
currentHeadingLevel=m[2].charAt(0)=="=" ? 1 : 0;
}
else
{
// Remove blank lines
text=text.replace(/(\r\n|\n|\r)/gm,"");
currentHeadingLevel=0;
}
}
if (headingLevel==-1)
headingLevel=(currentHeadingLevel+1) % 4;
// Removing a heading
var selOffset=0;
var selLen=0;
if (headingLevel==0)
{
// Deleting selection
if (text=="Heading")
{
state.ReplaceSelection("");
return true;
}
selLen=text.length;
selOffset=0;
}
else
{
if (text=="")
text="Heading";
selOffset=headingLevel+1;
selLen=text.length;
var h="";
for (var i=0; i<headingLevel; i++)
h+="#";
text=h + " " + text + " " + h;
}
// Require blank after
text+="\n\n";
if (state.m_selectionStart!=0)
{
text="\n\n" + text;
selOffset+=2;
}
// Replace text
state.ReplaceSelection(text);
// Update selection
state.m_selectionStart+=selOffset;
state.m_selectionEnd=state.m_selectionStart + selLen;
return true;
}
pub.cmd_heading = function(state) {
return this.setHeadingLevel(state, -1);
};
pub.cmd_h0 = function(state) {
return this.setHeadingLevel(state, 0);
};
pub.cmd_h1 = function(state) {
return this.setHeadingLevel(state, 1);
};
pub.cmd_h2 = function(state) {
return this.setHeadingLevel(state, 2);
};
pub.cmd_h3 = function(state) {
return this.setHeadingLevel(state, 3);
};
pub.cmd_h4 = function(state) {
return this.setHeadingLevel(state, 4);
};
pub.cmd_h5 = function(state) {
return this.setHeadingLevel(state, 5);
};
pub.cmd_h6 = function(state) {
return this.setHeadingLevel(state, 6);
};
priv.IndentCodeBlock=function(state, indent)
{
// Make sure whole lines are selected
state.SelectWholeLines();
// Get the text, split into lines
var lines=state.getSelectedText().split("\n");
// Convert leading tabs to spaces
for (var i=0; i<lines.length; i++)
{
if (lines[i].charAt(0)=="\t")
{
var newLead="";
var p=0;
while (lines[i].charAt(p)=="\t")
{
newLead+=" ";
p++;
}
var newLine=newLead + lines[i].substr(p);
lines.splice(i, 1, newLine);
}
}
// Toggle indent/unindent?
if (indent===null)
{
var i;
for (i=0; i<lines.length; i++)
{
// Blank lines are allowed
if (trim(lines[i])=="")
continue;
// Convert leading tabs to spaces
if (lines[i].charAt(0)=="\t")
{
var newLead="";
var p=0;
while (lines[i].charAt(p)=="\t")
{
newLead+=" ";
p++;
}
var newLine=newLead + lines[i].substr(i);
lines.splice(i, 1, newLine);
}
// Tabbed line
if (!starts_with(lines[i], " "))
break;
}
// Are we adding or removing indent
indent=i!=lines.length;
}
// Apply the changes
for (var i=0; i<lines.length; i++)
{
// Blank line?
if (trim(lines[i])=="")
continue;
// Tabbed line
var newline=lines[i];
if (indent)
{
newline=" " + lines[i];
}
else
{
if (starts_with(lines[i], "\t"))
newline=lines[i].substr(1);
else if (starts_with(lines[i], " "))
newline=lines[i].substr(4);
}
lines.splice(i, 1, newline);
}
// Replace
state.ReplaceSelection(lines.join("\n"));
}
// Code
pub.cmd_code=function(state)
{
// Cursor on a blank line?
if (state.m_selectionStart==state.m_selectionEnd)
{
var line=state.FindStartOfLine(state.m_selectionStart);
if (state.IsBlankLine(line))
{
state.SelectSurroundingWhiteSpace();
state.ReplaceSelection("\n\n Code\n\n");
state.m_selectionStart+=6;
state.m_selectionEnd=state.m_selectionStart + 4;
return true;
}
}
// If the current text is preceeded by a non-whitespace, or followed by a non-whitespace
// then do an inline code
if (state.getSelectedText().indexOf("\n")<0)
{
// Expand selection to include leading/trailing stars
state.TrimSelection();
if (state.PreceededBy("`"))
state.m_selectionStart--;
if (state.FollowedBy("`"))
state.m_selectionEnd++;
return this.bold_or_italic(state, "`");
}
this.IndentCodeBlock(state, null);
return true;
}
pub.cmd_tab=function(state)
{
if (state.getSelectedText().indexOf("\n")>0)
{
this.IndentCodeBlock(state, true);
}
else
{
// If we're in the leading whitespace of a line
// insert spaces instead of an actual tab character
var lineStart=state.FindStartOfLine(state.m_selectionStart);
var p;
for (p=lineStart; p<state.m_selectionStart; p++)
{
if (state.m_text.charAt(p)!=' ')
break;
}
// All spaces?
if (p==state.m_selectionStart)
{
var spacesToNextTabStop=4-((p-lineStart)%4);
state.ReplaceSelection(" ".substr(0, spacesToNextTabStop));
}
else
{
state.ReplaceSelection("\t");
}
state.m_selectionStart=state.m_selectionEnd;
}
return true;
}
pub.cmd_untab=function(state)
{
if (state.getSelectedText().indexOf("\n")>0)
{
this.IndentCodeBlock(state, false);
return true;
}
return false;
}
priv.bold_or_italic=function(state, marker)
{
var t=state.m_text;
var ml=marker.length;
// Work out if we're adding or removing bold markers
var text=state.getSelectedText();
if (starts_with(text, marker) && ends_with(text, marker))
{
// Remove
state.ReplaceSelection(text.substr(ml, text.length-ml*2));
}
else
{
// Add
state.TrimSelection();
text=state.getSelectedText();
if (!text)
text="text";
else
text=text.replace(/(\r\n|\n|\r)/gm,"");
state.ReplaceSelection(marker + text + marker);
state.InflateSelection(-ml, -ml);
}
return true;
}
// Bold
pub.cmd_bold=function(state)
{
if (!state.CheckSimpleSelection())
return false;
state.TrimSelection();
// Expand selection to include leading/trailing stars
if (state.PreceededBy("**"))
state.m_selectionStart-=2;
if (state.FollowedBy("**"))
state.m_selectionEnd+=2;
return this.bold_or_italic(state, "**");
}
// Italic
pub.cmd_italic=function(state)
{
if (!state.CheckSimpleSelection())
return false;
state.TrimSelection();
// Expand selection to include leading/trailing stars
if ((state.PreceededBy("*") && !state.PreceededBy("**")) || state.PreceededBy("***"))
state.m_selectionStart-=1;
if ((state.FollowedBy("*") && !state.PreceededBy("**")) || state.FollowedBy("***"))
state.m_selectionEnd+=1;
return this.bold_or_italic(state, "*");
}
priv.indent_or_outdent=function(state, outdent)
{
if (false && state.m_selectionStart==state.m_selectionEnd)
{
state.SelectSurroundingWhiteSpace();
state.ReplaceSelection("\n\n> Quote\n\n");
state.m_selectionStart+=4;
state.m_selectionEnd=state.m_selectionStart+5;
return true;
}
// Make sure whole lines are selected
state.SelectWholeLines();
// Get the text, split into lines and check if all lines
// are indented
var lines=state.getSelectedText().split("\n");
// Apply the changes
for (var i=0; i<lines.length-1; i++)
{
// Tabbed line
var newline=lines[i];
if (outdent)
{
if (starts_with(lines[i], "> "))
newline=lines[i].substr(2);
}
else
{
newline="> " + lines[i];
}
lines.splice(i, 1, newline);
}
// Replace
state.ReplaceSelection(lines.join("\n"));
return true;
}
// Quote
pub.cmd_indent=function(state)
{
return this.indent_or_outdent(state, false);
}
pub.cmd_outdent=function(state)
{
return this.indent_or_outdent(state, true);
}
priv.handle_list=function(state, type)
{
// Build an array of selected line offsets
var lines=[];
if (state.getSelectedText().indexOf("\n")>0)
{
state.SelectWholeLines();
var line=state.m_selectionStart;
lines.push(line);
while (true)
{
line=state.FindNextLine(line);
if (line>=state.m_selectionEnd)
break;
lines.push(line);
}
}
else
{
lines.push(state.FindStartOfLine(state.m_selectionStart));
}
// Now work out the new list type
// If the current selection only contains the current list type
// then remove list items
var prefix = type=="*" ? "* " : "1. ";
for (var i=0; i<lines.length; i++)
{
var lt=state.DetectListType(lines[i]);
if (lt.m_listType==type)
{
prefix="";
break;
}
}
// Update the prefix on all lines
for (var i=lines.length-1; i>=0; i--)
{
var line=lines[i];
var lt=state.DetectListType(line);
state.ReplaceAt(line, lt.m_prefixLen, prefix);
}
// We now need to find any surrounding lists and renumber them
var mdd=new MarkdownDeep.Markdown();
mdd.ExtraMode=true;
var listitems=mdd.GetListItems(state.m_text, state.m_selectionStart);
while (listitems!=null)
{
// Process each list item
var dx=0;
for (var i=0; i<listitems.length-1; i++)
{
// Detect the list type
var lt=state.DetectListType(listitems[i]+dx);
if (lt.m_listType!="1")
break;
// Format new number prefix
var newNumber=(i+1).toString() + ". ";
// Replace it
state.ReplaceAt(listitems[i]+dx, lt.m_prefixLen, newNumber);
// Adjust things if new prefix is different length to the previos
dx += newNumber.length - lt.m_prefixLen;
}
var newlistitems=mdd.GetListItems(state.m_text, listitems[listitems.length-1]+dx);
if (newlistitems!=null && newlistitems[0]!=listitems[0])
listitems=newlistitems;
else
listitems=null;
}
// Select lines
if (lines.length>1)
{
state.SelectWholeLines();
}
return true;
}
pub.cmd_ullist = function(state) {
return this.handle_list(state, "*");
};
pub.cmd_ollist = function(state) {
return this.handle_list(state, "1");
};
pub.cmd_link = function(ctx) {
ctx.TrimSelection();
if (!ctx.CheckSimpleSelection())
return false;
var url = prompt("Bitte gib eine URL an:");
if (url === null)
return false;
var text = ctx.getSelectedText();
if (text.length == 0) {
text = "Linktext";
}
var str = "[" + text + "](" + url + ")";
ctx.ReplaceSelection(str);
ctx.m_selectionStart++;
ctx.m_selectionEnd = ctx.m_selectionStart + text.length;
return true;
};
pub.cmd_img = function(ctx) {
ctx.TrimSelection();
if (!ctx.CheckSimpleSelection())
return false;
var url = prompt("Bitte gib eine Bild-Url an");
if (url === null)
return false;
var alttext = ctx.getSelectedText();
if (alttext.length == 0) {
alttext = "Bildtext";
}
var str = "![" + alttext + "](" + url + ")";
ctx.ReplaceSelection(str);
ctx.m_selectionStart += 2;
ctx.m_selectionEnd = ctx.m_selectionStart + alttext.length;
return true;
};
pub.cmd_hr = function(state) {
state.SelectSurroundingWhiteSpace();
if (state.m_selectionStart == 0)
state.ReplaceSelection("----------\n\n");
else
state.ReplaceSelection("\n\n----------\n\n");
state.m_selectionStart = state.m_selectionEnd;
;
return true;
};
pub.IndentNewLine = function() {
var editor = this;
var timer;
var handler = function() {
window.clearInterval(timer);
// Create an editor state from the current selection
var state = new EditorState();
state.InitFromTextArea(editor.m_textarea);
// Find start of previous line
var prevline = state.FindStartOfLine(state.SkipPreceedingEol(state.m_selectionStart));
// Count spaces and tabs
var i = prevline;
while (true) {
var ch = state.m_text.charAt(i);
if (ch != ' ' && ch != '\t')
break;
i++;
}
// Copy spaces and tabs to the new line
if (i > prevline) {
state.ReplaceSelection(state.m_text.substr(prevline, i - prevline));
state.m_selectionStart = state.m_selectionEnd;
}
state.Apply();
};
timer = window.setInterval(handler, 1);
return false;
};
pub.cmd_indented_newline = function(state) {
// Do default new line
state.ReplaceSelection("\n");
state.m_selectionStart = state.m_selectionEnd;
// Find start of previous line
var prevline = state.FindStartOfLine(state.SkipPreceedingEol(state.m_selectionStart));
// Count spaces and tabs
var i = prevline;
while (true) {
var ch = state.m_text.charAt(i);
if (ch != ' ' && ch != '\t')
break;
i++;
}
// Copy spaces and tabs to the new line
if (i > prevline) {
state.ReplaceSelection(state.m_text.substr(prevline, i - prevline));
state.m_selectionStart = state.m_selectionEnd;
}
return true;
};
// Handle toolbar button
pub.InvokeCommand = function(id) {
// Special handling for undo and redo
if (id == "undo" || id == "redo") {
this["cmd_" + id]();
this.m_textarea.focus();
return;
}
// Create an editor state from the current selection
var state = new EditorState();
state.InitFromTextArea(this.m_textarea);
// Create a copy for undo buffer
var originalState = state.Duplicate();
// Call the handler and apply changes
if (this["cmd_" + id](state)) {
// Save current state on undo stack
this.m_undoMode = undomode_unknown;
this.m_undoStack.splice(this.m_undoPos, this.m_undoStack.length - this.m_undoPos, originalState);
this.m_undoPos++;
// Apply new state
state.Apply();
// Update markdown rendering
this.onMarkdownChanged();
return true;
} else {
this.m_textarea.focus();
return false;
}
};
delete priv;
delete pub;
// Exports
this.Editor=Editor;
}();