Paste should result in a clean Markdown code
* jquery.htmlClean.min.js: * in order to paste from any(?) document * rangy-selectionsaverestore.js: required by hallo html cleanup * App.master: * A link to awesome fonts * a hint, to engage to invoice :-) * rangy-core.js: using the bower installed one * to-markdown.js: using the new code, came from the `npm start` process * Edit.aspx: fixes copy/paste from LibreOffice writter colored text. * Web.csproj: using new javascript references to: rangy-core, rangy-selectionsaverestore jquery.htmlClean * mdd_help.htm: * mdd_styles.css: * rangyinputs-jquery-1.1.2.js: obsoletevnext
parent
6bf32bf3c5
commit
f4329d6176
File diff suppressed because one or more lines are too long
@ -1,168 +0,0 @@
|
||||
<div class="panel">
|
||||
<h2>Markdown Formatting</h2>
|
||||
<p><a href="http://daringfireball.net/projects/markdown/" target="_blank">Markdown</a> turns plain text formatting into fancy HTML formatting.</p>
|
||||
<h3>Phrase Emphasis</h3>
|
||||
<pre><code>*italic* **bold**
|
||||
_italic_ __bold__
|
||||
</code></pre>
|
||||
|
||||
<h3>Links</h3>
|
||||
<p>Inline:</p>
|
||||
<pre><code>An [example](http://url.com/ "Title")
|
||||
</code></pre>
|
||||
|
||||
<p>Reference-style labels (titles are optional):</p>
|
||||
<pre><code>An [example][id]. Then, anywhere
|
||||
else in the doc, define the link:
|
||||
|
||||
[id]: http://example.com/ "Title"
|
||||
</code></pre>
|
||||
|
||||
<h3>Images</h3>
|
||||
<p>Inline (titles are optional):</p>
|
||||
<pre><code>![alt text](/path/img.jpg "Title")
|
||||
</code></pre>
|
||||
|
||||
<p>Reference-style:</p>
|
||||
<pre><code>![alt text][id]
|
||||
|
||||
[id]: /url/to/img.jpg "Title"
|
||||
</code></pre>
|
||||
|
||||
<h3>Headers</h3>
|
||||
<p>Setext-style:</p>
|
||||
<pre><code>Header 1
|
||||
========
|
||||
|
||||
Header 2
|
||||
--------
|
||||
</code></pre>
|
||||
|
||||
<p>atx-style (closing #'s are optional):</p>
|
||||
<pre><code># Header 1 #
|
||||
|
||||
## Header 2 ##
|
||||
|
||||
###### Header 6
|
||||
</code></pre>
|
||||
|
||||
<h3>Lists</h3>
|
||||
<p>Ordered, without paragraphs:</p>
|
||||
<pre><code>1. Foo
|
||||
2. Bar
|
||||
</code></pre>
|
||||
|
||||
<p>Unordered, with paragraphs:</p>
|
||||
<pre><code>* A list item.
|
||||
|
||||
With multiple paragraphs.
|
||||
|
||||
* Bar
|
||||
</code></pre>
|
||||
|
||||
<p>You can nest them:</p>
|
||||
<pre><code>* Abacus
|
||||
* answer
|
||||
* Bubbles
|
||||
1. bunk
|
||||
2. bupkis
|
||||
* BELITTLER
|
||||
3. burper
|
||||
* Cunning
|
||||
</code></pre>
|
||||
|
||||
<h3>Blockquotes</h3>
|
||||
<pre><code>> Email-style angle brackets
|
||||
> are used for blockquotes.
|
||||
|
||||
> > And, they can be nested.
|
||||
|
||||
> #### Headers in blockquotes
|
||||
>
|
||||
> * You can quote a list.
|
||||
> * Etc.
|
||||
</code></pre>
|
||||
|
||||
<h3>Code Spans</h3>
|
||||
<pre><code>`<code>` spans are delimited
|
||||
by backticks.
|
||||
|
||||
You can include literal backticks
|
||||
like `` `this` ``.
|
||||
</code></pre>
|
||||
|
||||
<h3>Preformatted Code Blocks</h3>
|
||||
<p>Indent every line of a code block by at least 4 spaces or 1 tab.</p>
|
||||
<pre><code>This is a normal paragraph.
|
||||
|
||||
This is a preformatted
|
||||
code block.
|
||||
</code></pre>
|
||||
|
||||
<h3>Horizontal Rules</h3>
|
||||
<p>Three or more dashes or asterisks:</p>
|
||||
<pre><code>---
|
||||
|
||||
* * *
|
||||
|
||||
- - - -
|
||||
</code></pre>
|
||||
|
||||
<h3>Manual Line Breaks</h3>
|
||||
<p>End a line with two or more spaces:</p>
|
||||
<pre><code>Roses are red,
|
||||
Violets are blue.
|
||||
</code></pre>
|
||||
|
||||
<h2>Extra Mode</h2>
|
||||
|
||||
These formatting features are only available when Extra Mode is enabled.
|
||||
|
||||
<h3>Markdown In Html</h3>
|
||||
<p>Enable markdown in HTML block level elements:</p>
|
||||
<pre><code><div markdown="1">
|
||||
Markdown **still** works.
|
||||
</div>
|
||||
</code></pre>
|
||||
|
||||
<h3>Fenced Code Blocks</h3>
|
||||
<p>Code blocks delimited by 3 or more tildas:</p>
|
||||
<pre><code>~~~
|
||||
This is a preformatted
|
||||
code block
|
||||
~~~
|
||||
</code></pre>
|
||||
|
||||
<h3>Header IDs</h3>
|
||||
<p>Set the id of headings with <code>{#<id>}</code> at end of heading line:</p>
|
||||
<pre><code>## My Heading {#myheading}
|
||||
</code></pre>
|
||||
|
||||
<h3>Tables</h3>
|
||||
|
||||
<pre><code>Fruit |Color
|
||||
---------|----------
|
||||
Apples |Red
|
||||
Pears |Green
|
||||
Bananas |Yellow</code></pre>
|
||||
<h3>Definition Lists</h3>
|
||||
<pre><code>Term 1
|
||||
: Definition 1
|
||||
|
||||
Term 2
|
||||
: Definition 2</code></pre>
|
||||
|
||||
<h3>Footnotes</h3>
|
||||
<pre><code>Body text with a footnote [^1]
|
||||
|
||||
[^1]: Footnote text here
|
||||
</code></pre>
|
||||
|
||||
<h3>Abbreviations</h3>
|
||||
<pre><code>MDD <- will have title
|
||||
|
||||
*[MDD]: MarkdownDeep
|
||||
</code></pre>
|
||||
<p> </p>
|
||||
|
||||
</div>
|
@ -1,211 +0,0 @@
|
||||
div.mdd_modal
|
||||
{
|
||||
position:absolute;
|
||||
top:0;
|
||||
left:0;
|
||||
padding:0;
|
||||
margin:0;
|
||||
width:100%;
|
||||
height:100%;
|
||||
z-index:1000;
|
||||
display:none;
|
||||
font-size:10pt;
|
||||
background-image:url(mdd_modal_background.png);
|
||||
}
|
||||
div.mdd_modal_frame
|
||||
{
|
||||
background-color:black;
|
||||
color:white;
|
||||
z-index:2000;
|
||||
margin:0 auto;
|
||||
margin-top:60px;
|
||||
border:solid 5px #808080;
|
||||
position:relative;
|
||||
border-radius:5px;
|
||||
-moz-border-radius:5px;
|
||||
-webkit-border-radius:5px;
|
||||
}
|
||||
|
||||
div.mdd_modal_button
|
||||
{
|
||||
position:absolute;
|
||||
top:-33px;
|
||||
right:-5px;
|
||||
padding-left:10px;
|
||||
padding-right:10px;
|
||||
padding-top:4px;
|
||||
padding-bottom:0px;
|
||||
height:20px;
|
||||
background-color:#202020;
|
||||
z-index:1999;
|
||||
border-radius:4px;
|
||||
-moz-border-radius:4px;
|
||||
-webkit-border-radius:4px;
|
||||
line-height:1em;
|
||||
}
|
||||
|
||||
div.mdd_modal_button a
|
||||
{
|
||||
color:Yellow;
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
div.mdd_modal_button a:hover
|
||||
{
|
||||
color:Orange;
|
||||
}
|
||||
|
||||
div.mdd_modal_content
|
||||
{
|
||||
overflow:scroll;
|
||||
overflow-x:hidden;
|
||||
position:relative;
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
div.mdd_ajax_loader
|
||||
{
|
||||
background-position: center center;
|
||||
background-image: url(mdd_ajax_loader.gif);
|
||||
background-repeat: no-repeat;
|
||||
width:100%;
|
||||
height:200px;
|
||||
}
|
||||
|
||||
div.mdd_syntax
|
||||
{
|
||||
font-size:12pt;
|
||||
padding:10px;
|
||||
}
|
||||
|
||||
div.mdd_syntax h2
|
||||
{
|
||||
font-size:14pt;
|
||||
}
|
||||
div.mdd_syntax h3
|
||||
{
|
||||
font-size:12pt;
|
||||
}
|
||||
div.mdd_syntax pre
|
||||
{
|
||||
font-size:10pt;
|
||||
border:solid 1px silver;
|
||||
padding:4px;
|
||||
background-color:#282828;
|
||||
}
|
||||
|
||||
div.mdd_toolbar_wrap
|
||||
{
|
||||
width:100%;
|
||||
}
|
||||
div.mdd_toolbar
|
||||
{
|
||||
padding:5px;
|
||||
height:20px;
|
||||
}
|
||||
|
||||
div.mdd_toolbar ul
|
||||
{
|
||||
margin:0;
|
||||
padding:0;
|
||||
}
|
||||
|
||||
div.mdd_toolbar li
|
||||
{
|
||||
float:left;
|
||||
margin:0;
|
||||
padding:0;
|
||||
list-style:none;
|
||||
background-color: #888;
|
||||
}
|
||||
|
||||
div.mdd_toolbar a.mdd_button
|
||||
{
|
||||
background-image:url(mdd_toolbar.png);
|
||||
width:20px;
|
||||
height:20px;
|
||||
display:block;
|
||||
}
|
||||
|
||||
span.mdd_sep
|
||||
{
|
||||
width:5px;
|
||||
height:20px;
|
||||
display:block;
|
||||
border-left:solid 1px #808080;
|
||||
margin-left:5px;
|
||||
}
|
||||
|
||||
#mdd_bold { background-position:-1px -1px;}
|
||||
#mdd_bold:hover { background-position:-1px -23px; }
|
||||
#mdd_italic { background-position:-23px -1px; }
|
||||
#mdd_italic:hover { background-position:-23px -23px; }
|
||||
#mdd_ullist { background-position:-177px -1px; }
|
||||
#mdd_ullist:hover { background-position:-177px -23px; }
|
||||
#mdd_ollist { background-position:-155px -1px; }
|
||||
#mdd_ollist:hover { background-position:-155px -23px; }
|
||||
#mdd_indent { background-position:-67px -1px; }
|
||||
#mdd_indent:hover { background-position:-67px -23px; }
|
||||
#mdd_outdent { background-position:-89px -1px; }
|
||||
#mdd_outdent:hover { background-position:-89px -23px; }
|
||||
#mdd_link { background-position:-45px -1px; }
|
||||
#mdd_link:hover { background-position:-45px -23px; }
|
||||
#mdd_img { background-position:-133px -1px; }
|
||||
#mdd_img:hover { background-position:-133px -23px; }
|
||||
#mdd_hr { background-position:-221px -1px; }
|
||||
#mdd_hr:hover { background-position:-221px -23px; }
|
||||
#mdd_code { background-position:-111px -1px; }
|
||||
#mdd_code:hover { background-position:-111px -23px; }
|
||||
#mdd_heading { background-position:-199px -1px; }
|
||||
#mdd_heading:hover { background-position:-199px -23px; }
|
||||
#mdd_undo { background-position:-243px -1px; }
|
||||
#mdd_undo:hover { background-position:-243px -23px; }
|
||||
#mdd_redo { background-position:-265px -1px; }
|
||||
#mdd_redo:hover { background-position:-265px -23px; }
|
||||
|
||||
div.mdd_links
|
||||
{
|
||||
float:right;
|
||||
}
|
||||
|
||||
div.mdd_links a
|
||||
{
|
||||
text-decoration:none;
|
||||
color:#a0a0a0;
|
||||
font-size:smaller;
|
||||
}
|
||||
div.mdd_links a:hover
|
||||
{
|
||||
color:white;
|
||||
}
|
||||
|
||||
div.mdd_editor_wrap
|
||||
{
|
||||
padding-right: 8px;
|
||||
}
|
||||
textarea.mdd_editor
|
||||
{
|
||||
width:100%;
|
||||
resize:none;
|
||||
margin:0;padding: 3px;
|
||||
}
|
||||
|
||||
div.mdd_resizer_wrap
|
||||
{
|
||||
width:100%;
|
||||
}
|
||||
div.mdd_resizer
|
||||
{
|
||||
background:#181818;
|
||||
background-image:url("mdd_gripper.png");
|
||||
background-position:center center;
|
||||
background-repeat:no-repeat;
|
||||
padding-left:2px;
|
||||
padding-right:2px;
|
||||
height:9px;
|
||||
border:solid 1px #303030;
|
||||
margin-top:-1px;
|
||||
cursor:n-resize;
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,252 @@
|
||||
/**
|
||||
* Selection save and restore module for Rangy.
|
||||
* Saves and restores user selections using marker invisible elements in the DOM.
|
||||
*
|
||||
* Part of Rangy, a cross-browser JavaScript range and selection library
|
||||
* https://github.com/timdown/rangy
|
||||
*
|
||||
* Depends on Rangy core.
|
||||
*
|
||||
* Copyright 2015, Tim Down
|
||||
* Licensed under the MIT license.
|
||||
* Version: 1.3.0
|
||||
* Build date: 10 May 2015
|
||||
*/
|
||||
(function(factory, root) {
|
||||
if (typeof define == "function" && define.amd) {
|
||||
// AMD. Register as an anonymous module with a dependency on Rangy.
|
||||
define(["./rangy-core"], factory);
|
||||
} else if (typeof module != "undefined" && typeof exports == "object") {
|
||||
// Node/CommonJS style
|
||||
module.exports = factory( require("rangy") );
|
||||
} else {
|
||||
// No AMD or CommonJS support so we use the rangy property of root (probably the global variable)
|
||||
factory(root.rangy);
|
||||
}
|
||||
})(function(rangy) {
|
||||
rangy.createModule("SaveRestore", ["WrappedRange"], function(api, module) {
|
||||
var dom = api.dom;
|
||||
var removeNode = dom.removeNode;
|
||||
var isDirectionBackward = api.Selection.isDirectionBackward;
|
||||
var markerTextChar = "\ufeff";
|
||||
|
||||
function gEBI(id, doc) {
|
||||
return (doc || document).getElementById(id);
|
||||
}
|
||||
|
||||
function insertRangeBoundaryMarker(range, atStart) {
|
||||
var markerId = "selectionBoundary_" + (+new Date()) + "_" + ("" + Math.random()).slice(2);
|
||||
var markerEl;
|
||||
var doc = dom.getDocument(range.startContainer);
|
||||
|
||||
// Clone the Range and collapse to the appropriate boundary point
|
||||
var boundaryRange = range.cloneRange();
|
||||
boundaryRange.collapse(atStart);
|
||||
|
||||
// Create the marker element containing a single invisible character using DOM methods and insert it
|
||||
markerEl = doc.createElement("span");
|
||||
markerEl.id = markerId;
|
||||
markerEl.style.lineHeight = "0";
|
||||
markerEl.style.display = "none";
|
||||
markerEl.className = "rangySelectionBoundary";
|
||||
markerEl.appendChild(doc.createTextNode(markerTextChar));
|
||||
|
||||
boundaryRange.insertNode(markerEl);
|
||||
return markerEl;
|
||||
}
|
||||
|
||||
function setRangeBoundary(doc, range, markerId, atStart) {
|
||||
var markerEl = gEBI(markerId, doc);
|
||||
if (markerEl) {
|
||||
range[atStart ? "setStartBefore" : "setEndBefore"](markerEl);
|
||||
removeNode(markerEl);
|
||||
} else {
|
||||
module.warn("Marker element has been removed. Cannot restore selection.");
|
||||
}
|
||||
}
|
||||
|
||||
function compareRanges(r1, r2) {
|
||||
return r2.compareBoundaryPoints(r1.START_TO_START, r1);
|
||||
}
|
||||
|
||||
function saveRange(range, direction) {
|
||||
var startEl, endEl, doc = api.DomRange.getRangeDocument(range), text = range.toString();
|
||||
var backward = isDirectionBackward(direction);
|
||||
|
||||
if (range.collapsed) {
|
||||
endEl = insertRangeBoundaryMarker(range, false);
|
||||
return {
|
||||
document: doc,
|
||||
markerId: endEl.id,
|
||||
collapsed: true
|
||||
};
|
||||
} else {
|
||||
endEl = insertRangeBoundaryMarker(range, false);
|
||||
startEl = insertRangeBoundaryMarker(range, true);
|
||||
|
||||
return {
|
||||
document: doc,
|
||||
startMarkerId: startEl.id,
|
||||
endMarkerId: endEl.id,
|
||||
collapsed: false,
|
||||
backward: backward,
|
||||
toString: function() {
|
||||
return "original text: '" + text + "', new text: '" + range.toString() + "'";
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function restoreRange(rangeInfo, normalize) {
|
||||
var doc = rangeInfo.document;
|
||||
if (typeof normalize == "undefined") {
|
||||
normalize = true;
|
||||
}
|
||||
var range = api.createRange(doc);
|
||||
if (rangeInfo.collapsed) {
|
||||
var markerEl = gEBI(rangeInfo.markerId, doc);
|
||||
if (markerEl) {
|
||||
markerEl.style.display = "inline";
|
||||
var previousNode = markerEl.previousSibling;
|
||||
|
||||
// Workaround for issue 17
|
||||
if (previousNode && previousNode.nodeType == 3) {
|
||||
removeNode(markerEl);
|
||||
range.collapseToPoint(previousNode, previousNode.length);
|
||||
} else {
|
||||
range.collapseBefore(markerEl);
|
||||
removeNode(markerEl);
|
||||
}
|
||||
} else {
|
||||
module.warn("Marker element has been removed. Cannot restore selection.");
|
||||
}
|
||||
} else {
|
||||
setRangeBoundary(doc, range, rangeInfo.startMarkerId, true);
|
||||
setRangeBoundary(doc, range, rangeInfo.endMarkerId, false);
|
||||
}
|
||||
|
||||
if (normalize) {
|
||||
range.normalizeBoundaries();
|
||||
}
|
||||
|
||||
return range;
|
||||
}
|
||||
|
||||
function saveRanges(ranges, direction) {
|
||||
var rangeInfos = [], range, doc;
|
||||
var backward = isDirectionBackward(direction);
|
||||
|
||||
// Order the ranges by position within the DOM, latest first, cloning the array to leave the original untouched
|
||||
ranges = ranges.slice(0);
|
||||
ranges.sort(compareRanges);
|
||||
|
||||
for (var i = 0, len = ranges.length; i < len; ++i) {
|
||||
rangeInfos[i] = saveRange(ranges[i], backward);
|
||||
}
|
||||
|
||||
// Now that all the markers are in place and DOM manipulation over, adjust each range's boundaries to lie
|
||||
// between its markers
|
||||
for (i = len - 1; i >= 0; --i) {
|
||||
range = ranges[i];
|
||||
doc = api.DomRange.getRangeDocument(range);
|
||||
if (range.collapsed) {
|
||||
range.collapseAfter(gEBI(rangeInfos[i].markerId, doc));
|
||||
} else {
|
||||
range.setEndBefore(gEBI(rangeInfos[i].endMarkerId, doc));
|
||||
range.setStartAfter(gEBI(rangeInfos[i].startMarkerId, doc));
|
||||
}
|
||||
}
|
||||
|
||||
return rangeInfos;
|
||||
}
|
||||
|
||||
function saveSelection(win) {
|
||||
if (!api.isSelectionValid(win)) {
|
||||
module.warn("Cannot save selection. This usually happens when the selection is collapsed and the selection document has lost focus.");
|
||||
return null;
|
||||
}
|
||||
var sel = api.getSelection(win);
|
||||
var ranges = sel.getAllRanges();
|
||||
var backward = (ranges.length == 1 && sel.isBackward());
|
||||
|
||||
var rangeInfos = saveRanges(ranges, backward);
|
||||
|
||||
// Ensure current selection is unaffected
|
||||
if (backward) {
|
||||
sel.setSingleRange(ranges[0], backward);
|
||||
} else {
|
||||
sel.setRanges(ranges);
|
||||
}
|
||||
|
||||
return {
|
||||
win: win,
|
||||
rangeInfos: rangeInfos,
|
||||
restored: false
|
||||
};
|
||||
}
|
||||
|
||||
function restoreRanges(rangeInfos) {
|
||||
var ranges = [];
|
||||
|
||||
// Ranges are in reverse order of appearance in the DOM. We want to restore earliest first to avoid
|
||||
// normalization affecting previously restored ranges.
|
||||
var rangeCount = rangeInfos.length;
|
||||
|
||||
for (var i = rangeCount - 1; i >= 0; i--) {
|
||||
ranges[i] = restoreRange(rangeInfos[i], true);
|
||||
}
|
||||
|
||||
return ranges;
|
||||
}
|
||||
|
||||
function restoreSelection(savedSelection, preserveDirection) {
|
||||
if (!savedSelection.restored) {
|
||||
var rangeInfos = savedSelection.rangeInfos;
|
||||
var sel = api.getSelection(savedSelection.win);
|
||||
var ranges = restoreRanges(rangeInfos), rangeCount = rangeInfos.length;
|
||||
|
||||
if (rangeCount == 1 && preserveDirection && api.features.selectionHasExtend && rangeInfos[0].backward) {
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(ranges[0], true);
|
||||
} else {
|
||||
sel.setRanges(ranges);
|
||||
}
|
||||
|
||||
savedSelection.restored = true;
|
||||
}
|
||||
}
|
||||
|
||||
function removeMarkerElement(doc, markerId) {
|
||||
var markerEl = gEBI(markerId, doc);
|
||||
if (markerEl) {
|
||||
removeNode(markerEl);
|
||||
}
|
||||
}
|
||||
|
||||
function removeMarkers(savedSelection) {
|
||||
var rangeInfos = savedSelection.rangeInfos;
|
||||
for (var i = 0, len = rangeInfos.length, rangeInfo; i < len; ++i) {
|
||||
rangeInfo = rangeInfos[i];
|
||||
if (rangeInfo.collapsed) {
|
||||
removeMarkerElement(savedSelection.doc, rangeInfo.markerId);
|
||||
} else {
|
||||
removeMarkerElement(savedSelection.doc, rangeInfo.startMarkerId);
|
||||
removeMarkerElement(savedSelection.doc, rangeInfo.endMarkerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
api.util.extend(api, {
|
||||
saveRange: saveRange,
|
||||
restoreRange: restoreRange,
|
||||
saveRanges: saveRanges,
|
||||
restoreRanges: restoreRanges,
|
||||
saveSelection: saveSelection,
|
||||
restoreSelection: restoreSelection,
|
||||
removeMarkerElement: removeMarkerElement,
|
||||
removeMarkers: removeMarkers
|
||||
});
|
||||
});
|
||||
|
||||
return rangy;
|
||||
}, this);
|
@ -1,274 +0,0 @@
|
||||
/**
|
||||
* @license Rangy Inputs, a jQuery plug-in for selection and caret manipulation within textareas and text inputs.
|
||||
*
|
||||
* https://github.com/timdown/rangyinputs
|
||||
*
|
||||
* For range and selection features for contenteditable, see Rangy.
|
||||
|
||||
* http://code.google.com/p/rangy/
|
||||
*
|
||||
* Depends on jQuery 1.0 or later.
|
||||
*
|
||||
* Copyright 2013, Tim Down
|
||||
* Licensed under the MIT license.
|
||||
* Version: 1.1.2
|
||||
* Build date: 6 September 2013
|
||||
*/
|
||||
(function($) {
|
||||
var UNDEF = "undefined";
|
||||
var getSelection, setSelection, deleteSelectedText, deleteText, insertText;
|
||||
var replaceSelectedText, surroundSelectedText, extractSelectedText, collapseSelection;
|
||||
|
||||
// Trio of isHost* functions taken from Peter Michaux's article:
|
||||
// http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting
|
||||
function isHostMethod(object, property) {
|
||||
var t = typeof object[property];
|
||||
return t === "function" || (!!(t == "object" && object[property])) || t == "unknown";
|
||||
}
|
||||
|
||||
function isHostProperty(object, property) {
|
||||
return typeof(object[property]) != UNDEF;
|
||||
}
|
||||
|
||||
function isHostObject(object, property) {
|
||||
return !!(typeof(object[property]) == "object" && object[property]);
|
||||
}
|
||||
|
||||
function fail(reason) {
|
||||
if (window.console && window.console.log) {
|
||||
window.console.log("RangyInputs not supported in your browser. Reason: " + reason);
|
||||
}
|
||||
}
|
||||
|
||||
function adjustOffsets(el, start, end) {
|
||||
if (start < 0) {
|
||||
start += el.value.length;
|
||||
}
|
||||
if (typeof end == UNDEF) {
|
||||
end = start;
|
||||
}
|
||||
if (end < 0) {
|
||||
end += el.value.length;
|
||||
}
|
||||
return { start: start, end: end };
|
||||
}
|
||||
|
||||
function makeSelection(el, start, end) {
|
||||
return {
|
||||
start: start,
|
||||
end: end,
|
||||
length: end - start,
|
||||
text: el.value.slice(start, end)
|
||||
};
|
||||
}
|
||||
|
||||
function getBody() {
|
||||
return isHostObject(document, "body") ? document.body : document.getElementsByTagName("body")[0];
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
var testTextArea = document.createElement("textarea");
|
||||
|
||||
getBody().appendChild(testTextArea);
|
||||
|
||||
if (isHostProperty(testTextArea, "selectionStart") && isHostProperty(testTextArea, "selectionEnd")) {
|
||||
getSelection = function(el) {
|
||||
var start = el.selectionStart, end = el.selectionEnd;
|
||||
return makeSelection(el, start, end);
|
||||
};
|
||||
|
||||
setSelection = function(el, startOffset, endOffset) {
|
||||
var offsets = adjustOffsets(el, startOffset, endOffset);
|
||||
el.selectionStart = offsets.start;
|
||||
el.selectionEnd = offsets.end;
|
||||
};
|
||||
|
||||
collapseSelection = function(el, toStart) {
|
||||
if (toStart) {
|
||||
el.selectionEnd = el.selectionStart;
|
||||
} else {
|
||||
el.selectionStart = el.selectionEnd;
|
||||
}
|
||||
};
|
||||
} else if (isHostMethod(testTextArea, "createTextRange") && isHostObject(document, "selection") &&
|
||||
isHostMethod(document.selection, "createRange")) {
|
||||
|
||||
getSelection = function(el) {
|
||||
var start = 0, end = 0, normalizedValue, textInputRange, len, endRange;
|
||||
var range = document.selection.createRange();
|
||||
|
||||
if (range && range.parentElement() == el) {
|
||||
len = el.value.length;
|
||||
|
||||
normalizedValue = el.value.replace(/\r\n/g, "\n");
|
||||
textInputRange = el.createTextRange();
|
||||
textInputRange.moveToBookmark(range.getBookmark());
|
||||
endRange = el.createTextRange();
|
||||
endRange.collapse(false);
|
||||
if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
|
||||
start = end = len;
|
||||
} else {
|
||||
start = -textInputRange.moveStart("character", -len);
|
||||
start += normalizedValue.slice(0, start).split("\n").length - 1;
|
||||
if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
|
||||
end = len;
|
||||
} else {
|
||||
end = -textInputRange.moveEnd("character", -len);
|
||||
end += normalizedValue.slice(0, end).split("\n").length - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return makeSelection(el, start, end);
|
||||
};
|
||||
|
||||
// Moving across a line break only counts as moving one character in a TextRange, whereas a line break in
|
||||
// the textarea value is two characters. This function corrects for that by converting a text offset into a
|
||||
// range character offset by subtracting one character for every line break in the textarea prior to the
|
||||
// offset
|
||||
var offsetToRangeCharacterMove = function(el, offset) {
|
||||
return offset - (el.value.slice(0, offset).split("\r\n").length - 1);
|
||||
};
|
||||
|
||||
setSelection = function(el, startOffset, endOffset) {
|
||||
var offsets = adjustOffsets(el, startOffset, endOffset);
|
||||
var range = el.createTextRange();
|
||||
var startCharMove = offsetToRangeCharacterMove(el, offsets.start);
|
||||
range.collapse(true);
|
||||
if (offsets.start == offsets.end) {
|
||||
range.move("character", startCharMove);
|
||||
} else {
|
||||
range.moveEnd("character", offsetToRangeCharacterMove(el, offsets.end));
|
||||
range.moveStart("character", startCharMove);
|
||||
}
|
||||
range.select();
|
||||
};
|
||||
|
||||
collapseSelection = function(el, toStart) {
|
||||
var range = document.selection.createRange();
|
||||
range.collapse(toStart);
|
||||
range.select();
|
||||
};
|
||||
} else {
|
||||
getBody().removeChild(testTextArea);
|
||||
fail("No means of finding text input caret position");
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up
|
||||
getBody().removeChild(testTextArea);
|
||||
|
||||
deleteText = function(el, start, end, moveSelection) {
|
||||
var val;
|
||||
if (start != end) {
|
||||
val = el.value;
|
||||
el.value = val.slice(0, start) + val.slice(end);
|
||||
}
|
||||
if (moveSelection) {
|
||||
setSelection(el, start, start);
|
||||
}
|
||||
};
|
||||
|
||||
deleteSelectedText = function(el) {
|
||||
var sel = getSelection(el);
|
||||
deleteText(el, sel.start, sel.end, true);
|
||||
};
|
||||
|
||||
extractSelectedText = function(el) {
|
||||
var sel = getSelection(el), val;
|
||||
if (sel.start != sel.end) {
|
||||
val = el.value;
|
||||
el.value = val.slice(0, sel.start) + val.slice(sel.end);
|
||||
}
|
||||
setSelection(el, sel.start, sel.start);
|
||||
return sel.text;
|
||||
};
|
||||
|
||||
var updateSelectionAfterInsert = function(el, startIndex, text, selectionBehaviour) {
|
||||
var endIndex = startIndex + text.length;
|
||||
|
||||
selectionBehaviour = (typeof selectionBehaviour == "string") ?
|
||||
selectionBehaviour.toLowerCase() : "";
|
||||
|
||||
if ((selectionBehaviour == "collapsetoend" || selectionBehaviour == "select") && /[\r\n]/.test(text)) {
|
||||
// Find the length of the actual text inserted, which could vary
|
||||
// depending on how the browser deals with line breaks
|
||||
var normalizedText = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
||||
endIndex = startIndex + normalizedText.length;
|
||||
var firstLineBreakIndex = startIndex + normalizedText.indexOf("\n");
|
||||
|
||||
if (el.value.slice(firstLineBreakIndex, firstLineBreakIndex + 2) == "\r\n") {
|
||||
// Browser uses \r\n, so we need to account for extra \r characters
|
||||
endIndex += normalizedText.match(/\n/g).length;
|
||||
}
|
||||
}
|
||||
|
||||
switch (selectionBehaviour) {
|
||||
case "collapsetostart":
|
||||
setSelection(el, startIndex, startIndex);
|
||||
break;
|
||||
case "collapsetoend":
|
||||
setSelection(el, endIndex, endIndex);
|
||||
break;
|
||||
case "select":
|
||||
setSelection(el, startIndex, endIndex);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
insertText = function(el, text, index, selectionBehaviour) {
|
||||
var val = el.value;
|
||||
el.value = val.slice(0, index) + text + val.slice(index);
|
||||
if (typeof selectionBehaviour == "boolean") {
|
||||
selectionBehaviour = selectionBehaviour ? "collapseToEnd" : "";
|
||||
}
|
||||
updateSelectionAfterInsert(el, index, text, selectionBehaviour);
|
||||
};
|
||||
|
||||
replaceSelectedText = function(el, text, selectionBehaviour) {
|
||||
var sel = getSelection(el), val = el.value;
|
||||
el.value = val.slice(0, sel.start) + text + val.slice(sel.end);
|
||||
updateSelectionAfterInsert(el, sel.start, text, selectionBehaviour || "collapseToEnd");
|
||||
};
|
||||
|
||||
surroundSelectedText = function(el, before, after, selectionBehaviour) {
|
||||
if (typeof after == UNDEF) {
|
||||
after = before;
|
||||
}
|
||||
var sel = getSelection(el), val = el.value;
|
||||
el.value = val.slice(0, sel.start) + before + sel.text + after + val.slice(sel.end);
|
||||
var startIndex = sel.start + before.length;
|
||||
updateSelectionAfterInsert(el, startIndex, sel.text, selectionBehaviour || "select");
|
||||
};
|
||||
|
||||
function jQuerify(func, returnThis) {
|
||||
return function() {
|
||||
var el = this.jquery ? this[0] : this;
|
||||
var nodeName = el.nodeName.toLowerCase();
|
||||
|
||||
if (el.nodeType == 1 && (nodeName == "textarea" || (nodeName == "input" && el.type == "text"))) {
|
||||
var args = [el].concat(Array.prototype.slice.call(arguments));
|
||||
var result = func.apply(this, args);
|
||||
if (!returnThis) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
if (returnThis) {
|
||||
return this;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
$.fn.extend({
|
||||
getSelection: jQuerify(getSelection, false),
|
||||
setSelection: jQuerify(setSelection, true),
|
||||
collapseSelection: jQuerify(collapseSelection, true),
|
||||
deleteSelectedText: jQuerify(deleteSelectedText, true),
|
||||
deleteText: jQuerify(deleteText, true),
|
||||
extractSelectedText: jQuerify(extractSelectedText, false),
|
||||
insertText: jQuerify(insertText, true),
|
||||
replaceSelectedText: jQuerify(replaceSelectedText, true),
|
||||
surroundSelectedText: jQuerify(surroundSelectedText, true)
|
||||
});
|
||||
});
|
||||
})(jQuery);
|
Loading…
Reference in New Issue