├── README ├── copylink.html ├── copylink.js ├── copylink.min.js ├── selectionmenu.css ├── selectionmenu.html ├── selectionmenu.js └── selectionmenu.min.js /README: -------------------------------------------------------------------------------- 1 | Deutsche Beschreibung: 2 | ---------------------- 3 | 4 | SelectionMenu und CopyLink sind kleine, framework-unabhängige JavaScripte. 5 | 6 | SelectionMenu zeigt beim Markieren von Text ein Kontextmenü an. Die Idee und 7 | Umsetzung ähnelt der auf nytimes.com. Das Script ist jedoch einfacher und universell 8 | einsetzbar. 9 | 10 | CopyLink fügt eine Quellenreferenz ein, wenn der Benutzer Text markiert und in die 11 | Zwischenablage kopiert. Wenn beispielsweise »Franz jagt im komplett verwahrlosten Taxi« 12 | kopiert wird, so wird die URL der aktuellen Webseite an den kopierten Text angehangen: 13 | 14 | »Franz jagt im komplett verwahrlosten Taxi 15 | Quelle: http://example.net/article1234« 16 | 17 | Beide Scripte verwenden W3C DOM Range 18 | in modernen Browsern sowie 19 | Microsoft TextRanges 20 | für ältere Internet-Explorer-Versionen. 21 | 22 | Ausführliche Dokumentation: 23 | http://molily.de/weblog/selectionmenu-copylink 24 | 25 | Autor: Mathias Schäfer (molily) 26 | Lizenz: Public Domain 27 | 28 | 29 | English Description: 30 | -------------------- 31 | 32 | SelectionMenu and CopyLink are small self-contained, framework-agnostic scripts 33 | which demonstrate the abilities of the JavaScript APIs DOM Range and Microsoft 34 | TextRanges. 35 | 36 | SelectionMenu shows a context menu when the user selects some text on the page. 37 | This menu may offer a search feature, dictionary lookup, post to Facebook or similar. 38 | The idea and the implementation resembles the selection context menu on nytimes.com, 39 | but the script is way simpler and easy to integrate. 40 | 41 | CopyLink inserts a source reference when the user copys a piece of text into the 42 | clipboard. For example, if the user selects the text “The quick brown fox” and copys 43 | it, the URL of the current page will be appended: 44 | 45 | “The quick brown fox 46 | Source: http://example.net/article1234” 47 | 48 | Both scripts are using W3C DOM Range 49 | in modern browsers 50 | and Microsoft TextRanges 51 | as a fallback for older Internet Explorer versions. 52 | 53 | Author: Mathias Schäfer (molily) 54 | License: Public Domain 55 | 56 | 57 | SelectionMenu Usage: 58 | -------------------- 59 | 60 | Create an instance of SelectionMenu by calling “new SelectionMenu”. 61 | 62 | Pass an object literal with the following options: 63 | container (DOM element): 64 | The element where the copy event is observed. Normally that's 65 | the main text container. 66 | menuHTML (string): 67 | A string of HTML for the menu e.g. a list of links. 68 | handler (function): 69 | A handler function which is called when the user clicks on the menu. 70 | Use the passed click event to access the click link and respond to 71 | the user's action. 72 | minimalSelection (number, optional): 73 | Only display the menu if the selected text has at least this length. 74 | Defaults to 5 characters. 75 | id (string, optional): 76 | The ID of the menu element which is inserted. Defaults to “selection-menu”. 77 | 78 | Example: 79 | -------- 80 | 81 | This observes copy events at the element with the ID “article”. It inserts a menu 82 | with two links which both have IDs to recognize them. In the handler function, the 83 | selected text is read. Depending on the clicked link, the selected text is 84 | looked up on Google or Bing. 85 | 86 | new SelectionMenu({ 87 | container : document.getElementById('article'), 88 | menuHTML : 'Google SearchBing Search', 89 | handler : function (e) { 90 | var target = e.target || e.srcElement, 91 | id = target.id, 92 | selectedText = this.selectedText, 93 | query = encodeURI(selectedText.replace(/\s/g, '+')), 94 | searchURI; 95 | 96 | if (id == 'selection-menu-google') { 97 | searchURI = 'http://www.google.com/search?ie=utf-8&q='; 98 | } else if (id == 'selection-menu-bing') { 99 | searchURI = 'http://www.bing.com/search?q='; 100 | } 101 | 102 | location.href = searchURI + query; 103 | } 104 | }); 105 | 106 | 107 | CopyLink Usage: 108 | --------------- 109 | 110 | Create an instance of SelectionMenu by calling “new CopyLink”. 111 | 112 | Pass an object literal with the following options: 113 | container (DOM element): 114 | The element where the copy event is observed. Normally that's 115 | the main text container. 116 | handler (function, optional): 117 | A handler function which is called when the user copys some text. 118 | This function should return a string which is appended to the 119 | copied text. 120 | Defaults to '
Source: ' + location.href, e.g. “Source:” and the 121 | URL of the current document. 122 | minimalSelection (number, optional): 123 | Only append a source reference if the selected text has at least this length. 124 | Defaults to 20 characters. 125 | 126 | Example: 127 | -------- 128 | 129 | This observes copy events at the element with the ID “article”. It appends the 130 | string “Source: ” when the user copys text which is longer than 20 characters. 131 | 132 | new CopyLink({ 133 | container : document.getElementById('article'), 134 | handler : function () { 135 | return '
Source: ' + location.href; 136 | } 137 | }); 138 | -------------------------------------------------------------------------------- /copylink.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CopyLink Example 6 | 18 | 19 | 31 | 32 | 33 | 34 |
35 | 36 |

Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

37 | 38 |

Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.

39 | 40 |

Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.

41 | 42 |

Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.

43 | 44 |

Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis.

45 | 46 |
    47 |
  • At vero eos et accusam et justo duo dolores et ea rebum.
  • 48 |
  • Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
  • 49 |
  • Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt
  • 50 |
  • ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
  • 51 |
52 | 53 |

Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.

54 | 55 |

Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

56 | 57 |
58 | 59 | 60 | -------------------------------------------------------------------------------- /copylink.js: -------------------------------------------------------------------------------- 1 | /* 2 | CopyLink 1.1 3 | http://github.com/molily/selectionmenu 4 | by molily (molily@mailbox.org, http://molily.de/) 5 | 6 | EN: CopyLink automatically inserts a source reference when the user copies text from a page 7 | DE: CopyLink fügt automatisch eine Quellenreferenz ein, wenn der Benutzer Text von einer Seite kopiert 8 | 9 | EN: License: Public Domain 10 | EN: You're allowed to copy, distribute and change the code without restrictions 11 | 12 | DE: Lizenz: Public Domain 13 | DE: Kopieren, Verteilen und Aendern ohne Einschraenkungen erlaubt 14 | */ 15 | 16 | // EN: Create a private scope using an anonymous function, 17 | // EN: save the return value in a global variable. 18 | // DE: Erzeuge einen privaten Scope durch eine anonyme Funktion, 19 | // DE: speichere den Rückgabwert in einer globalen Variable 20 | var CopyLink = (function (window, document) { 21 | 22 | // EN: The element which is inserted when copying 23 | // DE: Das Element, welche beim Kopieren eingefügt wird 24 | var span = null; 25 | 26 | // EN: Shared private helper functions 27 | // DE: Geteilte private Helferfunktionen 28 | 29 | function addEvent (obj, type, fn) { 30 | // EN: Feature dection DOM Events / Microsoft 31 | // DE: Fähigkeitenweiche DOM Events / Microsoft 32 | if (obj.addEventListener) { 33 | obj.addEventListener(type, fn, false); 34 | } else if (obj.attachEvent) { 35 | obj.attachEvent('on' + type, function () { 36 | return fn.call(obj, window.event); 37 | }); 38 | } 39 | } 40 | 41 | // EN: Publish addEvent as a static method 42 | // EN: (attach it to the constructor object) 43 | // DE: Mache addEvent als statische Methode öffentlich 44 | // DE: (hefte die Methode an den Konstruktor, der zurückgegeben wird) 45 | CopyLink.addEvent = addEvent; 46 | 47 | function getSelection () { 48 | // EN: Feature dection HTML5 / Microsoft 49 | // DE: Fähigkeitenweiche HTML5 / Microsoft 50 | if (window.getSelection) { 51 | return window.getSelection(); 52 | } else if (document.selection && document.selection.createRange) { 53 | return document.selection.createRange(); 54 | } else { 55 | // EN: No browser support available for the required features 56 | // DE: Keine Browser-Unterstützung für die benötigten Features 57 | return false; 58 | } 59 | } 60 | 61 | function getSelectedText (selection) { 62 | // EN : Feature detection HTML5 / Microsoft 63 | // DE: Fähigkeitenweiche HTML5 / Microsoft 64 | return selection.toString ? selection.toString() : selection.text; 65 | } 66 | 67 | function removeSpan () { 68 | // EN: Is the element attached to the DOM tree? 69 | // DE: Ist das Element in den DOM-Baum gehängt? 70 | var parent = span.parentNode; 71 | if (parent) { 72 | // EN: Remove the element from DOM (the element object remains 73 | // EN: in memory and will be reused later) 74 | // DE: Entferne das element aus dem DOM-Baum (Element bleibt im Speicher erhalten 75 | // DE: und wird später wiederverwendet) 76 | parent.removeChild(span); 77 | } 78 | } 79 | 80 | // EN: Main constructor function 81 | // DE: Konstruktorfunktion 82 | function CopyLink (options) { 83 | var instance = this; 84 | 85 | // EN: Copy members from the options object to the instance 86 | // DE: Kopiere Einstellungen aus dem options-Objekt herüber zur Instanz 87 | instance.id = options.id || 'copylink'; 88 | instance.minimalSelection = options.minimalSelection || 20; 89 | instance.container = options.container; 90 | instance.handler = options.handler || function () { 91 | return '
Source: ' + location.href; 92 | }; 93 | 94 | // EN: Initialisation 95 | // DE: Initialisiere 96 | instance.create(); 97 | instance.setupEvents(); 98 | } 99 | 100 | CopyLink.prototype = { 101 | 102 | create : function () { 103 | var instance = this; 104 | 105 | // EN: Create the container for the inserted text if necessary 106 | // DE: Erzeuge den Container für den eingefügten Text, sofern noch nicht passiert 107 | if (span) { 108 | return; 109 | } 110 | span = document.createElement('span'); 111 | span.id = instance.id; 112 | span.style.cssText = 'position: absolute; left: -9999px;'; 113 | }, 114 | 115 | setupEvents : function () { 116 | var instance = this; 117 | addEvent(instance.container, 'copy', function () { 118 | instance.insert(); 119 | }); 120 | }, 121 | 122 | insert : function () { 123 | var instance = this; 124 | 125 | // EN: Get a Selection object or a TextRange (IE) 126 | // DE: Hole Selection bzw. TextRange (IE) 127 | var selection = getSelection(); 128 | if (!selection) { 129 | // EN: No browser support available for the required features 130 | // DE: Keine Browser-Unterstützung für die benötigten Features 131 | return; 132 | } 133 | 134 | // EN: Get the selected text 135 | // DE: Hole markierten Text 136 | var selectedText = getSelectedText(selection); 137 | 138 | // EN: Abort if the selected text is too short 139 | // DE: Breche ab, wenn der markierte Text zu kurz ist 140 | if (selectedText.length < instance.minimalSelection) { 141 | return; 142 | } 143 | 144 | // EN : Feature detection DOM Range / Microsoft 145 | // DE: Fähigkeitenweiche DOM Range / Microsoft 146 | if (selection.getRangeAt) { 147 | 148 | // EN: W3C DOM Range approach 149 | // DE: Lösungsansatz mit W3C DOM Range 150 | 151 | // EN: Get the first Range of the current Selection 152 | // DE: Hole Range, die zur Selection gehört 153 | var range = selection.getRangeAt(0); 154 | 155 | // EN: Get the start and end nodes of the selection 156 | // DE: Hole Start- und Endknoten der Auswahl 157 | var startNode = range.startContainer; 158 | var endNode = range.endContainer; 159 | 160 | if (!(startNode && endNode && startNode.compareDocumentPosition)) { 161 | // EN: Abort if we got bogus values or we can't compare their document position 162 | // DE: Breche ab, wenn die Knoten nicht brauchbar sind 163 | return; 164 | } 165 | 166 | // EN: If the start node succeeds the end node in the DOM tree, flip them 167 | // DE: Wenn von hinten nach vorne markiert wurde, drehe Start und Ende um 168 | if (startNode.compareDocumentPosition(endNode) & 2) { 169 | startNode = endNode; 170 | endNode = range.startContainer; 171 | } 172 | 173 | // EN: Get the start and end offset 174 | // DE: Hole Start- und End-Offset 175 | var startOffset = range.startOffset; 176 | var endOffset = range.endOffset; 177 | 178 | // EN: If the end node is an element, use its last text node as the end offset 179 | // DE: Falls der Endknoten ein Element ist, nehme das Ende des letzten Textknoten 180 | if (endNode.nodeType == 1) { 181 | endNode = endNode.lastChild; 182 | if (!endNode || endNode.nodeType != 3) { 183 | return; 184 | } 185 | endOffset = endNode.data.length; 186 | } 187 | 188 | // EN: Create a new empty Range 189 | // DE: Erzeuge neue, leere Range 190 | var newRange = document.createRange(); 191 | 192 | // EN: Move the beginning of the new Range to the end of the selection 193 | // DE: Verschiebe Anfang der neuen Range an das Ende der Auswahl 194 | newRange.setStart(endNode, endOffset); 195 | 196 | // EN: Fill the span containing the source reference 197 | // DE: Befülle das span-Element mit der Quellenreferenz 198 | span.innerHTML = instance.handler.call(instance); 199 | 200 | // EN: Inject the span element into the new Range 201 | // DE: Füge das span-Element in die neue Range ein 202 | newRange.insertNode(span); 203 | 204 | // EN: Enlarge the Range forward to enclose the original Range 205 | // DE: Erweitere Range nach vorne, um die ursprüngliche einzuschließen 206 | 207 | // EN: Include the span into the selection 208 | // DE: Schließe span in die Auswahl ein 209 | range.setEndAfter(span); 210 | 211 | // EN: Now select the whole text in the new range. 212 | // EN: This text is copied to the clipboard. 213 | // DE: Markiere den Text der Range, damit dieser in 214 | // DE: die Zwischenablage kopiert wird 215 | selection.removeAllRanges(); 216 | selection.addRange(range); 217 | 218 | window.setTimeout(function () { 219 | // EN: Remove the span from the selection 220 | // DE: Entferne span wieder aus der Auswahl 221 | range.setEndBefore(span); 222 | 223 | // EN: Restore the original range and its text selection 224 | // DE: Stelle die ursprüngliche Range und damit die ursprüngliche Textauswahl wieder her 225 | if (selection.removeRange) { 226 | selection.removeRange(range); 227 | } else { 228 | selection.removeAllRanges(); 229 | } 230 | selection.addRange(range); 231 | 232 | // EN: Remove the span from the DOM tree 233 | // DE: Entferne span wieder aus dem DOM 234 | removeSpan(); 235 | }, 0); 236 | 237 | } else if (selection.duplicate) { 238 | 239 | // EN: Microsoft TextRange approach 240 | // DE: Lösungsansatz mit Microsoft TextRanges 241 | 242 | // EN: Create a copy the the TextRange 243 | // DE: Kopiere TextRange 244 | var newRange = selection.duplicate(); 245 | 246 | // EN: Move the start of the new range to the end of the selection 247 | // DE: Verschiebe den Anfang der neuen Range an das Ende der Auswahl 248 | newRange.setEndPoint('StartToEnd', selection); 249 | 250 | // EN: Fill the span containing the source reference 251 | // DE: Befülle das span-Element mit der Quellenreferenz 252 | span.innerHTML = instance.handler.call(instance); 253 | 254 | // EN: Insert the span into the new range 255 | // DE: Fülle die neue Range mit dem span 256 | newRange.pasteHTML(span.outerHTML); 257 | 258 | // EN: Add the new TextRange to the current selection 259 | // DE: Schließe eingefügte TextRange in die Auswahl ein 260 | selection.setEndPoint('EndToEnd', newRange); 261 | 262 | // EN: Select the text of the TextRange 263 | // EN: This text is copied to the clipboard. 264 | // DE: Markiere den Textinhalt der TextRange, damit dieser in 265 | // DE: die Zwischenablage kopiert wird 266 | selection.select(); 267 | 268 | // EN: Since we're using outerHTML to insert the span element, 269 | // EN: we have to restore the span reference 270 | // DE: Da das Befüllen nicht über das DOM, sondern über serialisierten HTML-Code erfolgt, 271 | // DE: stelle die Referenz wieder her 272 | span = document.getElementById(id); 273 | 274 | // EN: Remove the span from the DOM tree 275 | // DE: Entferne span wieder aus dem DOM 276 | window.setTimeout(removeSpan, 0); 277 | 278 | } 279 | 280 | // EN: No browser support available for the required features 281 | // DE: Keine Browser-Unterstützung für die benötigten Features 282 | 283 | } 284 | }; 285 | 286 | // EN: Return the constructor function 287 | // DE: Gib den Konstruktor zurück 288 | return CopyLink; 289 | 290 | })(window, document); -------------------------------------------------------------------------------- /copylink.min.js: -------------------------------------------------------------------------------- 1 | var CopyLink=function(h,f){function j(a,b,d){a.addEventListener?a.addEventListener(b,d,!1):a.attachEvent&&a.attachEvent("on"+b,function(){return d.call(a,h.event)})}function k(){var a=b.parentNode;a&&a.removeChild(b)}function i(a){this.id=a.id||"copylink";this.minimalSelection=a.minimalSelection||20;this.container=a.container;this.handler=a.handler||function(){return"
Source: "+location.href};this.create();this.setupEvents()}var b=null;i.addEvent=j;i.prototype={create:function(){if(!b)b=f.createElement("span"), 2 | b.id=this.id,b.style.cssText="position: absolute; left: -9999px;"},setupEvents:function(){var a=this;j(a.container,"copy",function(){a.insert()})},insert:function(){var a=h.getSelection?h.getSelection():f.selection&&f.selection.createRange?f.selection.createRange():!1;if(a&&!((a.toString?a.toString():a.text).length 2 | 3 | 4 | 5 | SelectionMenu Example 6 | 7 | 19 | 20 | 46 | 47 | 48 | 49 |
50 | 51 |

Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

52 | 53 |

Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.

54 | 55 |

Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.

56 | 57 |

Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.

58 | 59 |

Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis.

60 | 61 |
    62 |
  • At vero eos et accusam et justo duo dolores et ea rebum.
  • 63 |
  • Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
  • 64 |
  • Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt
  • 65 |
  • ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
  • 66 |
67 | 68 |

Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.

69 | 70 |

Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

71 | 72 |
73 | 74 | 75 | -------------------------------------------------------------------------------- /selectionmenu.js: -------------------------------------------------------------------------------- 1 | /* 2 | SelectionMenu 1.1 3 | http://github.com/molily/selectionmenu 4 | by molily (molily@mailbox.org, http://molily.de/) 5 | 6 | EN: SelectionMenu displays a context menu when the user selects some text on the page 7 | DE: SelectionMenu blendet ein Kontextmenü beim Markieren von Text ein 8 | 9 | EN: License: Public Domain 10 | EN: You're allowed to copy, distribute and change the code without restrictions 11 | 12 | DE: Lizenz: Public Domain 13 | DE: Kopieren, Verteilen und Aendern ohne Einschraenkungen erlaubt 14 | */ 15 | 16 | // EN: Create a private scope using an anonymous function, 17 | // EN: save the return value in a global variable. 18 | // DE: Erzeuge einen privaten Scope durch eine anonyme Funktion, 19 | // DE: speichere den Rückgabwert in einer globalen Variable 20 | var SelectionMenu = (function (window, document) { 21 | 22 | // EN: The menu element which is inserted when selecting text 23 | // DE: Das Menü-Element, welche beim Markieren eingefügt wird 24 | var span = null; 25 | 26 | // EN: Shared private helper functions 27 | // DE: Geteilte private Helferfunktionen 28 | 29 | function addEvent (obj, type, fn) { 30 | // EN: Feature dection DOM Events / Microsoft 31 | // DE: Fähigkeitenweiche DOM Events / Microsoft 32 | if (obj.addEventListener) { 33 | obj.addEventListener(type, fn, false); 34 | } else if (obj.attachEvent) { 35 | obj.attachEvent('on' + type, function () { 36 | return fn.call(obj, window.event); 37 | }); 38 | } 39 | } 40 | 41 | // EN: Publish addEvent as a static method 42 | // EN: (attach it to the constructor object) 43 | // DE: Mache addEvent als statische Methode öffentlich 44 | // DE: (hefte die Methode an den Konstruktor, der zurückgegeben wird) 45 | SelectionMenu.addEvent = addEvent; 46 | 47 | function getSelection () { 48 | // EN: Feature dection HTML5 / Microsoft 49 | // DE: Fähigkeitenweiche HTML5 / Microsoft 50 | if (window.getSelection) { 51 | return window.getSelection(); 52 | } else if (document.selection && document.selection.createRange) { 53 | return document.selection.createRange(); 54 | } else { 55 | // EN: No browser support available for the required features 56 | // DE: Keine Browser-Unterstützung für die benötigten Features 57 | return false; 58 | } 59 | } 60 | 61 | function getSelectedText (selection) { 62 | // EN: Feature detection HTML5 / Microsoft 63 | // DE: Fähigkeitenweiche HTML5 / Microsoft 64 | return selection.toString ? selection.toString() : selection.text; 65 | } 66 | 67 | 68 | function contains (a, b) { 69 | // EN: Feature detection DOM Core / Microsoft 70 | // DE: Fähigkeitenweiche DOM Core / Microsoft 71 | return a.compareDocumentPosition ? !!(a.compareDocumentPosition(b) & 16) : a.contains(b); 72 | } 73 | 74 | function mouseOnMenu (e) { 75 | // Greife auf das Zielelement des Ereignisses zu 76 | // EN: Feature detection DOM Events / Microsoft 77 | // DE: Fähigkeitenweiche DOM Events / Microsoft 78 | var target = e.target || e.srcElement; 79 | // Ist das Zielelement das Menü oder darin enthalten? 80 | return target == span || contains(span, target); 81 | } 82 | 83 | // EN: Main constructor function 84 | // DE: Konstruktorfunktion 85 | function SelectionMenu (options) { 86 | var instance = this; 87 | 88 | // EN: Copy members from the options object to the instance 89 | // DE: Kopiere Einstellungen aus dem options-Objekt herüber zur Instanz 90 | instance.id = options.id || 'selection-menu'; 91 | instance.menuHTML = options.menuHTML; 92 | instance.minimalSelection = options.minimalSelection || 5; 93 | instance.container = options.container; 94 | instance.handler = options.handler; 95 | 96 | // EN: Initialisation 97 | // DE: Initialisiere 98 | instance.create(); 99 | instance.setupEvents(); 100 | } 101 | 102 | SelectionMenu.prototype = { 103 | 104 | create : function () { 105 | var instance = this; 106 | 107 | // EN: Create the menu container if necessary 108 | // DE: Erzeuge den Menü-Container, sofern noch nicht passiert 109 | if (span) { 110 | return; 111 | } 112 | 113 | span = document.createElement('span'); 114 | span.id = instance.id; 115 | }, 116 | 117 | setupEvents : function () { 118 | 119 | var instance = this; 120 | var container = instance.container; 121 | 122 | // EN: Hide the menu on mouse down 123 | // DE: Verstecke beim Mousedown 124 | addEvent(container, 'mousedown', function (e) { 125 | instance.hide(e); 126 | }); 127 | 128 | // EN: Insert the menu on mouseup given some text is selected 129 | // DE: Füge das Menü beim Mouseup ein, wenn Text ausgewählt wurde 130 | addEvent(container, 'mouseup', function (e) { 131 | instance.insert(e); 132 | 133 | // EN: After a delay, check if the text was deselected 134 | // DE: Prüfe nach einer Verzögerung, ob die Auswahl damit aufgehoben wurde 135 | window.setTimeout(function () { 136 | instance.hideIfNoSelection(); 137 | }, 0); 138 | 139 | }); 140 | 141 | instance.setupMenuEvents(); 142 | }, 143 | 144 | setupMenuEvents : function () { 145 | var instance = this; 146 | 147 | // EN: Register the handler for clicks on the menu 148 | // DE: Registiere Handlerfunktion für den Klick auf das Menü 149 | addEvent(span, 'click', function (e) { 150 | instance.handler.call(instance, e); 151 | return false; 152 | }); 153 | 154 | // EN: Prevent IE to select the text of the menu 155 | // DE: Verhindere das Markieren des Menüs im IE 156 | span.unselectable = true; 157 | }, 158 | 159 | hide : function (e) { 160 | // EN: Abort if an event object was passed and the click hit the menu itself 161 | // Breche ab, wenn Event-Objekt übergeben wurde und der Klick beim Menü passierte 162 | if (e && mouseOnMenu(e)) { 163 | return; 164 | } 165 | // EN: Is the element attached to the DOM tree? 166 | // DE: Ist das Element in den DOM-Baum gehängt? 167 | var parent = span.parentNode; 168 | if (parent) { 169 | // EN: Remove the element from DOM (the element object remains 170 | // EN: in memory and will be reused later) 171 | // DE: Entferne das element aus dem DOM-Baum (Element bleibt im Speicher erhalten 172 | // DE: und wird später wiederverwendet) 173 | parent.removeChild(span); 174 | } 175 | }, 176 | 177 | hideIfNoSelection : function () { 178 | var instance = this; 179 | var selection = getSelection(); 180 | if (!selection) { 181 | return; 182 | } 183 | var selectedText = getSelectedText(selection); 184 | if (!selectedText.length) { 185 | instance.hide(); 186 | } 187 | }, 188 | 189 | insert : function (e) { 190 | var instance = this; 191 | 192 | // EN: Abort if the mouse event occured at the menu itself 193 | // DE: Breche ab, wenn das Mausereignis beim Menü passierte 194 | if (mouseOnMenu(e)) { 195 | return; 196 | } 197 | 198 | // EN: Get a Selection object or a TextRange (IE) 199 | // DE: Hole Selection bzw. TextRange (IE) 200 | var selection = getSelection(); 201 | if (!selection) { 202 | // EN: No browser support available for the required features 203 | // DE: Keine Browser-Unterstützung für die benötigten Features 204 | return; 205 | } 206 | 207 | // EN: Get the selected text 208 | // DE: Hole markierten Text 209 | var selectedText = getSelectedText(selection); 210 | instance.selectedText = selectedText; 211 | 212 | // EN: Abort if the selected text is too short 213 | // DE: Breche ab, wenn der markierte Text zu kurz ist 214 | if (selectedText.length < instance.minimalSelection) { 215 | instance.hide(e); 216 | return; 217 | } 218 | 219 | // EN : Feature detection DOM Range / Microsoft 220 | // DE: Fähigkeitenweiche DOM Range / Microsoft 221 | if (selection.getRangeAt) { 222 | 223 | // EN: W3C DOM Range approach 224 | // DE: Lösungsansatz mit W3C DOM Range 225 | 226 | // EN: Get the first Range of the current Selection 227 | // DE: Hole Range, die zur Selection gehört 228 | var range = selection.getRangeAt(0); 229 | 230 | // EN: Get the start and end nodes of the selection 231 | // DE: Hole Start- und Endknoten der Auswahl 232 | var startNode = range.startContainer; 233 | var endNode = range.endContainer; 234 | 235 | if (!(startNode && endNode && startNode.compareDocumentPosition)) { 236 | // EN: Abort if we got bogus values or we can't compare their document position 237 | // DE: Breche ab, wenn die Knoten nicht brauchbar sind 238 | return; 239 | } 240 | 241 | // EN: If the start node succeeds the end node in the DOM tree, flip them 242 | // DE: Wenn von hinten nach vorne markiert wurde, drehe Start und Ende um 243 | if (startNode.compareDocumentPosition(endNode) & 2) { 244 | startNode = endNode; 245 | endNode = range.startContainer; 246 | } 247 | 248 | // EN: Get the end offset 249 | // DE: Hole End-Offset 250 | var endOffset = range.endOffset; 251 | 252 | // EN: If the end node is an element, use its last text node as the end offset 253 | // DE: Falls der Endknoten ein Element ist, nehme das Ende des letzten Textknoten 254 | if (endNode.nodeType == 1) { 255 | endNode = endNode.lastChild; 256 | if (!endNode || endNode.nodeType != 3) { 257 | return; 258 | } 259 | endOffset = endNode.data.length; 260 | } 261 | 262 | // EN: Create a new empty Range 263 | // DE: Erzeuge neue, leere Range 264 | var newRange = document.createRange(); 265 | 266 | // EN: Move the beginning of the new Range to the end of the selection 267 | // DE: Verschiebe Anfang der neuen Range an das Ende der Auswahl 268 | newRange.setStart(endNode, endOffset); 269 | 270 | // EN: Fill the menu span 271 | // DE: Befülle das Menü-span 272 | span.innerHTML = instance.menuHTML; 273 | 274 | // EN: Inject the span element into the new Range 275 | // DE: Füge das span-Element in die neue Range ein 276 | newRange.insertNode(span); 277 | 278 | // EN: Adjust the selection by removing and adding the range. 279 | // EN: This prevents the selection of the menu text. 280 | // DE: Korrigiere Auswahl, verhindere das Markieren des Menüs 281 | if (selection.removeRange) { 282 | selection.removeRange(range); 283 | } else { 284 | selection.removeAllRanges(); 285 | } 286 | selection.addRange(range); 287 | 288 | } else if (selection.duplicate) { 289 | 290 | // EN: Microsoft TextRange approach 291 | // DE: Lösungsansatz mit Microsoft TextRanges 292 | 293 | // EN: Create a copy the the TextRange 294 | // DE: Kopiere TextRange 295 | var newRange = selection.duplicate(); 296 | 297 | // EN: Move the start of the new range to the end of the selection 298 | // DE: Verschiebe den Anfang der neuen Range an das Ende der Auswahl 299 | newRange.setEndPoint('StartToEnd', selection); 300 | 301 | // EN: Fill the menu span 302 | // DE: Befülle das Menü-span 303 | span.innerHTML = instance.menuHTML; 304 | 305 | // EN: Insert the span into the new range 306 | // DE: Fülle die neue Range mit dem span 307 | newRange.pasteHTML(span.outerHTML); 308 | 309 | // EN: Restore the selection so that the original text is selected 310 | // EN: and not the menu 311 | // DE: Korrigiere Auswahl und setze sie auf die ursprüngliche Auswahl zurück, 312 | // DE: sodass das Menü nicht selektiert ist 313 | selection.select(); 314 | 315 | // EN: Since we're using outerHTML to insert the span element, 316 | // EN: we have to restore the span reference and the event handling 317 | // DE: Da das Befüllen nicht über das DOM, sondern über serialisierten HTML-Code erfolgt, 318 | // DE: stelle die Referenz und das Event-Handling wieder her 319 | span = document.getElementById(id); 320 | instance.setupMenuEvents(); 321 | 322 | } else { 323 | // EN: No browser support available for the required features 324 | // DE: Keine Browser-Unterstützung für die benötigten Features 325 | return; 326 | } 327 | 328 | // EN: Menu positioning 329 | // DE: Positioniere Menü 330 | instance.position(); 331 | }, 332 | 333 | position : function () { 334 | span.style.marginTop = -(span.offsetHeight + 5) + 'px'; 335 | } 336 | }; 337 | 338 | // EN: Return the constructor function 339 | // DE: Gib den Konstruktor zurück 340 | return SelectionMenu; 341 | 342 | })(window, document); -------------------------------------------------------------------------------- /selectionmenu.min.js: -------------------------------------------------------------------------------- 1 | var SelectionMenu=function(h,e){function i(a,b,d){a.addEventListener?a.addEventListener(b,d,!1):a.attachEvent&&a.attachEvent("on"+b,function(){return d.call(a,h.event)})}function k(){return h.getSelection?h.getSelection():e.selection&&e.selection.createRange?e.selection.createRange():!1}function l(a){a=a.target||a.srcElement;return a==c||(c.compareDocumentPosition?!!(c.compareDocumentPosition(a)&16):c.contains(a))}function j(a){this.id=a.id||"selection-menu";this.menuHTML=a.menuHTML;this.minimalSelection= 2 | a.minimalSelection||5;this.container=a.container;this.handler=a.handler;this.create();this.setupEvents()}var c=null;j.addEvent=i;j.prototype={create:function(){if(!c)c=e.createElement("span"),c.id=this.id},setupEvents:function(){var a=this,b=a.container;i(b,"mousedown",function(b){a.hide(b)});i(b,"mouseup",function(b){a.insert(b);h.setTimeout(function(){a.hideIfNoSelection()},0)});a.setupMenuEvents()},setupMenuEvents:function(){var a=this;i(c,"click",function(b){a.handler.call(a,b);return!1});c.unselectable= 3 | !0},hide:function(a){if(!a||!l(a))(a=c.parentNode)&&a.removeChild(c)},hideIfNoSelection:function(){var a=k();a&&((a.toString?a.toString():a.text).length||this.hide())},insert:function(a){if(!l(a)){var b=k();if(b){var d=b.toString?b.toString():b.text;this.selectedText=d;if(d.length