├── 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 |