Make Bookmarklets from Javascript URLs

When it sees a link to a userscript or general Javascript URL, adds a Bookmarklet besides it, which you can drag to your toolbar to load the script when you next need it (running outside Greasemonkey of course).

当前为 2015-08-28 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Make Bookmarklets from Javascript URLs
  3. // @namespace MBFU
  4. // @description When it sees a link to a userscript or general Javascript URL, adds a Bookmarklet besides it, which you can drag to your toolbar to load the script when you next need it (running outside Greasemonkey of course).
  5. // @include http://hwi.ath.cx/*/gm_scripts/*
  6. // @include http://hwi.ath.cx/*/userscripts/*
  7. // @include http://*userscripts.org/*
  8. // @include https://*userscripts.org/*
  9. // @include http://openuserjs.org/*
  10. // @include https://openuserjs.org/*
  11. // @include http://greasyfork.org/*
  12. // @include https://greasyfork.org/*
  13. // @include http://*/*
  14. // @include https://*/*
  15. // @exclude http://hwi.ath.cx/code/other/gm_scripts/joeys_userscripts_and_bookmarklets_overview.html
  16. // @exclude http://neuralyte.org/~joey/gm_scripts/joeys_userscripts_and_bookmarklets_overview.html
  17. // @grant none
  18. // @version 1.2.5
  19. // ==/UserScript==
  20.  
  21. // BUG: We had (i%32) in a userscript (DLT) but when this was turned into a bookmarklet and dragged into Chrome, the debugger showed it had become (i2), causing the script to error with "i2 is not defined". Changing the code to (i % 32) worked around the problem.
  22.  
  23. // TODO: All links with using dummy=random should change the value on mouseover/mousemove/click, so they can be re-used live without refreshing the page.
  24. // Static bookmarklets could timeout and re-build after ... 10 seconds? ^^
  25.  
  26. // Most people will NOT want the NoCache version. It's only useful for script developers.
  27.  
  28. // Firefox/Greasemonkey does not like to install scripts ending ".user.js?dummy=123"
  29. // But in Chrome it is a useful way to prevent the script from being cached when you are developing it.
  30. var inGoogleChrome = window && window.navigator && window.navigator.vendor.match(/Google/);
  31.  
  32. // It doesn't make much sense for me to make it browser-dependent, because I share my bookmarks between Chrome and Firefox (using XMarks).
  33. var preventBrowserFromCachingBookmarklets = inGoogleChrome;
  34. //var preventBrowserFromCachingBookmarklets = true;
  35.  
  36. // Sometimes Chrome refuses to acknowledge that a script has been updated, and repeatedly installs an old version from its cache!
  37. var preventCachingOfInstallScripts = inGoogleChrome;
  38. // BUG: Chrome sometimes installs a new instance of an extension for each click, rather than overwriting (upgrading) the old. However disabling preventBrowserFromCachingBookmarklets does not fix that. It may have been the name "Wikimedia+"?
  39.  
  40. var addGreasemonkeyLibToBookmarklets = true;
  41.  
  42. // DONE: All bookmarklets optionally preload the Fallback GMAPI.
  43. // DONE: All bookmarklets optionally load in non-caching fashion (for changing scripts).
  44.  
  45. // DONE: Use onload event to ensure prerequisite scripts are loaded before dependent scripts.
  46.  
  47. // DONE via FBGMAPI: We could provide neat catches for GM_ API commands so they won't fail entirely.
  48.  
  49. // TODO: Provide extra feature, which allows the Bookmarks to actually trigger
  50. // the userscript properly-running inside Greasemonkey, if this userscript is
  51. // present to handle the request, otherwise (with warning) load outside GM.
  52.  
  53. // TODO: Support @include/@excude and @require meta rules?
  54. // This requires parsing the script's header using XHR before creating each bookmarklet.
  55.  
  56. // DONE: Optionally create static bookmarklet, with all code inline, rather than loaded from a URL.
  57. // // comments will need to be removed or converted to /*...*/ comments
  58.  
  59. var addDateToStaticBookmarklets = true;
  60.  
  61. var defaultScripts = [];
  62. var includeGMCompat = addGreasemonkeyLibToBookmarklets;
  63. if (includeGMCompat) {
  64. // defaultScripts.push("http://hwi.ath.cx/code/other/gm_scripts/fallbackgmapi/fallbackgmapi.user.js");
  65. defaultScripts.push("//neuralyte.org/~joey/gm_scripts/fallbackgmapi/fallbackgmapi.user.js");
  66. }
  67.  
  68. function buildLiveBookmarklet(link) {
  69. var neverCache = preventBrowserFromCachingBookmarklets;
  70.  
  71. var scriptsToLoad = defaultScripts.slice(0);
  72.  
  73. // The bookmarklet should load scripts using the same protocol as the page. Otherwise browsers may give error messages about "mixed content", and refuse to load the script.
  74. var url = link.href.replace(/^http[s]*:/, "//");
  75. scriptsToLoad.push(url);
  76.  
  77. var neverCacheStr = ( neverCache ? "+'?dummy='+new Date().getTime()" : "" );
  78.  
  79. /*
  80. var toRun = "(function(){\n";
  81. for (var i=0;i<scriptsToLoad.length;i++) {
  82. var script = scriptsToLoad[i];
  83. toRun += " var newScript = document.createElement('script');\n";
  84. toRun += " newScript.src = '" + script + "'" + neverCacheStr + ";\n";
  85. toRun += " document.body.appendChild(newScript);\n";
  86. }
  87. toRun += "})();";
  88. */
  89.  
  90. var toRun = "(function(){\n";
  91. // Chrome has no .toSource() or uneval(), so we use JSON. :f
  92. toRun += "var scriptsToLoad="+JSON.stringify(scriptsToLoad)+";\n";
  93. toRun += "function loadNext() {\n";
  94. toRun += " if (scriptsToLoad.length == 0) { return; }\n";
  95. toRun += " var next = scriptsToLoad.shift();\n";
  96. toRun += " var newScript = document.createElement('script');\n";
  97. toRun += " newScript.src = next"+neverCacheStr+";\n";
  98. toRun += " newScript.onload = loadNext;\n";
  99. toRun += " newScript.onerror = function(e){ console.error('Problem loading script: '+next,e); };\n";
  100. toRun += " document.body.appendChild(newScript);\n";
  101. toRun += "}\n";
  102. toRun += "loadNext();\n";
  103. toRun += "})(); (void 0);";
  104.  
  105. var name = getNameFromFilename(link.href);
  106. /*
  107. if (neverCache) {
  108. name = name + " (NoCache)";
  109. }
  110. if (includeGMCompat) {
  111. name = name + " (FBAPI)";
  112. }
  113. */
  114.  
  115. var newLink = document.createElement("A");
  116. newLink.href = "javascript:" + toRun;
  117. newLink.textContent = name;
  118. newLink.title = newLink.href;
  119.  
  120. var newContainer = document.createElement("div");
  121. // newContainer.style.whiteSpace = 'nowrap';
  122. newContainer.appendChild(document.createTextNode("(Live Bookmarklet: "));
  123. newContainer.appendChild(newLink);
  124. var extraString = ( neverCache || includeGMCompat ? neverCache && includeGMCompat ? " (no-caching, with GM fallbacks)" : neverCache ? " (no-caching)" : " (with GM fallbacks)" : "" );
  125. // DISABLED HERE:
  126. extraString = "";
  127. if (extraString) {
  128. // newContainer.appendChild(document.createTextNode(extraString));
  129. // var extraText = document.createElement("span");
  130. // extraText.style.fontSize = '80%';
  131. var extraText = document.createElement("small");
  132. extraText.textContent = extraString;
  133. newContainer.appendChild(extraText);
  134. }
  135. newContainer.appendChild(document.createTextNode(")"));
  136. newContainer.style.paddingLeft = '8px';
  137. // link.parentNode.insertBefore(newContainer,link.nextSibling);
  138. return newContainer;
  139. }
  140.  
  141. function reportMessage(msg) {
  142. console.log(msg);
  143. }
  144.  
  145. function reportWarning(msg) {
  146. console.warn(msg);
  147. }
  148.  
  149. function reportError(msg) {
  150. console.error(msg);
  151. }
  152.  
  153. function doesItCompile(code) {
  154. try {
  155. var f = new Function(code);
  156. } catch (e) {
  157. return false;
  158. }
  159. return true;
  160. }
  161.  
  162. /* For more bugs try putting (function(){ ... })(); wrapper around WikiIndent! */
  163.  
  164. function fixComments(line) {
  165.  
  166. //// Clear // comment line
  167. line = line.replace(/^[ \t]*\/\/.*/g,'');
  168.  
  169. //// Wrap // comment in /*...*/ (Dangerous! if comment contains a */ !)
  170. // line = line.replace(/^([ \t]*)\/\/(.*)/g,'$1/*$2*/');
  171.  
  172. //// Clear trailing comment (after a few chars we deem sensible)
  173. //// This still doesn't handle some valid cases.
  174. var trailingComment = /([;{}()\[\],\. \t])\s*\/\/.*/g;
  175. //// What might break: An odd number of "s or 's, any /*s or */s
  176. var worrying = /(["']|\/\*|\*\/)/;
  177. // Here is a breaking example:
  178. // var resNodes = document.evaluate("//div[@id='res']//li", document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
  179. // var hasTrailingComment = line.match(trailingComment);
  180. var hasTrailingComment = trailingComment.exec(line);
  181. if (hasTrailingComment) {
  182. /*
  183. if (line.match(worrying)) {
  184. reportWarning("Warning: trailingComment matches: "+hasTrailingComment);
  185. }
  186. */
  187. var compiledBefore = doesItCompile(line);
  188. var newLine = line.replace(trailingComment,'$1');
  189. var compilesAfter = doesItCompile(newLine);
  190. if (compiledBefore && !compilesAfter) {
  191. reportWarning("Aborted // stripping on: "+line);
  192. } else {
  193. // Accept changes
  194. line = newLine;
  195. }
  196. }
  197. return line;
  198.  
  199. }
  200.  
  201. function cleanupSource(source) {
  202. // console.log("old length: "+source.length);
  203. var lines = source.split('\n');
  204. // console.log("lines: "+lines.length);
  205. for (var i=0;i<lines.length;i++) {
  206. lines[i] = fixComments(lines[i]);
  207. }
  208. // source = lines.join('\n');
  209. source = lines.join(' ');
  210. // console.log("new length: "+source.length);
  211. //// For Bookmarklet Builder's reformatter:
  212. source = source.replace("(function","( function",'g');
  213. return source;
  214. }
  215.  
  216. // We could cache urls, at least during this one page visit
  217. // Specifically the ones we request repeatedly for statis bmls (fbgmapi).
  218. var sourcesLoaded = {};
  219.  
  220. // My first "promise":
  221. function getSourceFor(url) {
  222. return {
  223. then: function(handleResponse){
  224. // Prevent caching of this resource. I found this was needed one time on Chrome!
  225. // It probably doesn't need to be linked to preventCachingOfInstallScripts or preventBrowserFromCachingBookmarklets.
  226. url += "?dummy="+Math.random();
  227. console.log("Loading "+url);
  228. getURLThen(url, handlerFn, onErrorFn);
  229.  
  230. function handlerFn(res) {
  231. var source = res.responseText;
  232. handleResponse(source);
  233. }
  234.  
  235. function onErrorFn(res) {
  236. reportError("Failed to load "+url+": HTTP "+res.status);
  237. }
  238.  
  239. // CONSIDER: We should return the next promise?
  240. }
  241. };
  242. }
  243.  
  244. /* To avoid a multitude of premature network requests, the bookmarklet is not actually "compiled" until mouseover. */
  245. function buildStaticBookmarklet(link) {
  246.  
  247. var newLink = document.createElement("a");
  248. newLink.textContent = getNameFromFilename(link.href);
  249.  
  250. var newContainer = document.createElement("div");
  251. // newContainer.style.whiteSpace = 'nowrap';
  252. // Experimental:
  253. newContainer.appendChild(document.createTextNode("(Static Bookmarklet: "));
  254. newContainer.appendChild(newLink);
  255. newContainer.appendChild(document.createTextNode(")"));
  256. newContainer.style.paddingLeft = '8px';
  257.  
  258. newLink.style.textDecoration = 'underline';
  259. // newLink.style.color = '#000080';
  260. newLink.style.color = '#333366';
  261.  
  262. // link.parentNode.insertBefore(newContainer,link.nextSibling);
  263.  
  264. // The href may change before we fire (e.g. if dummy is appended) so we make a copy.
  265. var href = link.href;
  266.  
  267. // setTimeout(function(){
  268. // ,2000 * staticsRequested);
  269. newLink.onmouseover = function(){
  270. getStaticBookmarkletFromUserscript(href,whenGot);
  271. newLink.style.color = '#ff7700';
  272. newLink.onmouseover = null; // once
  273. };
  274.  
  275. function whenGot(staticSrc, report) {
  276.  
  277. var extraText = "";
  278.  
  279. // Does it parse?
  280. try {
  281. var testFn = new Function(staticSrc);
  282. newLink.style.color = ''; // Success! Color like a normal link
  283. } catch (e) {
  284. var msg = "PARSE FAILED";
  285. // Firefox has this:
  286. if (e.lineNumber) {
  287. msg += " on line "+e.lineNumber;
  288. }
  289. msg += ":";
  290. console.log("["+href+"] "+msg,e);
  291. newLink.title = msg+" "+e;
  292. newLink.style.color = 'red'; // color as error
  293. window.lastParseError = e;
  294. // return;
  295. }
  296.  
  297. newLink.href = "javascript:" + staticSrc;
  298.  
  299. if (addDateToStaticBookmarklets) {
  300. var d = new Date();
  301. //var dateStr = d.getFullYear()+"."+(d.getMonth()+1)+"."+d.getDate();
  302. var dateStr = d.toISOString().substring(0,10);
  303. newLink.textContent = newLink.textContent + " ("+dateStr+")";
  304. }
  305.  
  306. if (report.needsGMFallbacks) {
  307. extraText += " uses GM";
  308. } else {
  309. extraText += " GM free";
  310. }
  311.  
  312. if (extraText) {
  313. newLink.parentNode.insertBefore( document.createTextNode(extraText), newLink.nextSibling);
  314. }
  315.  
  316. // Just in case the browser is dumb, force it to re-analyse the link.
  317. newLink.parentNode.insertBefore(newLink, newLink.nextSibling);
  318. }
  319.  
  320. return newContainer;
  321.  
  322. }
  323.  
  324. function getStaticBookmarkletFromUserscript(href, callbackGotStatic) {
  325.  
  326. getSourceFor(href).then(function (userscriptSource) {
  327. var hasGrantNone = userscriptSource.match('\\n//\\s*@grant\\s+none\\s*\\n');
  328. var needsGMFallbacks = ! hasGrantNone;
  329.  
  330. var scriptsToLoad = needsGMFallbacks ? defaultScripts.slice(0) : [];
  331.  
  332. loadScripts(scriptsToLoad, allSourcesLoaded);
  333.  
  334. function allSourcesLoaded(scriptSources) {
  335. scriptSources.push(userscriptSource);
  336.  
  337. var toRun = "";
  338. for (var i=0; i<scriptSources.length; i++) {
  339. if (!scriptSources[i]) {
  340. reportError("Expected contents of "+scriptsToLoad[i]+" but got: "+scriptSources[i]);
  341. }
  342. var cleaned = cleanupSource(scriptSources[i]);
  343. toRun += "(function(){\n";
  344. toRun += cleaned;
  345. toRun += "})();\n";
  346. }
  347. toRun += "(void 0);";
  348.  
  349. var report = {
  350. needsGMFallbacks: needsGMFallbacks,
  351. };
  352.  
  353. callbackGotStatic(toRun, report);
  354. }
  355. });
  356.  
  357. function loadScripts(scriptsToLoad, callbackScriptsLoaded) {
  358. if (scriptsToLoad.length === 0) {
  359. return callbackScriptsLoaded([]);
  360. }
  361.  
  362. var scriptSources = [];
  363. var numLoaded = 0;
  364.  
  365. for (var i=0; i<scriptsToLoad.length; i++) {
  366. loadSourceIntoArray(i);
  367. }
  368.  
  369. function loadSourceIntoArray(i) {
  370. getSourceFor(scriptsToLoad[i]).then(function(source){
  371. if (!source) {
  372. reportError("Failed to acquire source for: "+href);
  373. }
  374. scriptSources[i] = source;
  375. numLoaded++;
  376. if (numLoaded == scriptsToLoad.length) {
  377. callbackScriptsLoaded(scriptSources);
  378. }
  379. });
  380. }
  381. }
  382.  
  383. }
  384.  
  385. // In this case, link points to a folder containing a userscript.
  386. // We guess the userscript's name from the folder's name.
  387. function addQuickInstall(link) {
  388. if (link.parentNode.tagName == 'TD') {
  389. link.parentNode.style.width = '60%';
  390. }
  391. var br2 = document.createElement("br");
  392. link.parentNode.insertBefore(br2,link.nextSibling);
  393. var br = document.createElement("br");
  394. link.parentNode.insertBefore(br,link.nextSibling.nextSibling);
  395. var name = link.href.match(/([^\/]*)\/$/)[1];
  396. var newLink = document.createElement("A");
  397. newLink.href = link.href + name+".user.js";
  398. newLink.textContent = "Install Userscript"; // name+".user.js";
  399. var newContainer = document.createElement("span");
  400. newContainer.appendChild(document.createTextNode(" ["));
  401. newContainer.appendChild(newLink);
  402. newContainer.appendChild(document.createTextNode("]"));
  403. newContainer.style.paddingLeft = '8px';
  404. link.parentNode.insertBefore(newContainer,br);
  405. link.style.color = 'black';
  406. link.style.fontWeight = 'bold';
  407. newContainer.appendChild(buildLiveBookmarklet(newLink));
  408. newContainer.appendChild(buildStaticBookmarklet(newLink));
  409. newContainer.appendChild(buildLiveUserscript(newLink));
  410. popupSourceOnHover(newLink);
  411. // Do this after the other two builders have used the .href
  412. if (preventCachingOfInstallScripts) {
  413. newLink.href = newLink.href + '?dummy='+new Date().getTime();
  414. }
  415. }
  416.  
  417. function getURLThen(url,handlerFn,onErrorFn) {
  418. var req = new XMLHttpRequest();
  419. req.open("get", url, true);
  420. req.onreadystatechange = function (aEvt) {
  421. if (req.readyState == 4) {
  422. if(req.status == 200) {
  423. // Got it
  424. handlerFn(req);
  425. } else {
  426. var msg = ("XHR failed with status "+req.status+"\n");
  427. window.status = msg;
  428. onErrorFn(req);
  429. console.warn(msg);
  430. }
  431. }
  432. };
  433. req.send(null);
  434. }
  435.  
  436. var frame = null;
  437.  
  438. function loadSourceViewer(url, newLink, evt) {
  439.  
  440. // window.lastEVT = evt;
  441.  
  442. if (frame && frame.parentNode) {
  443. frame.parentNode.removeChild(frame);
  444. }
  445. frame = document.createElement("div");
  446.  
  447. function reportToFrame(msg) {
  448. frame.appendChild( document.createTextNode(msg) );
  449. }
  450.  
  451. // This doesn't work. Loading it directly into the iframe triggers Greasemonkey to install it!
  452. //frame.src = url;
  453. // What we need to do is get the script with an XHR, then place it into the div.
  454.  
  455. // This seems to fire immediately in Firefox!
  456. var cleanup = function(evt) {
  457. frame.parentNode.removeChild(frame);
  458. document.body.removeEventListener("click",cleanup,false);
  459. // frame.removeEventListener("mouseout",cleanup,false);
  460. };
  461. document.body.addEventListener("click",cleanup,false);
  462.  
  463. getURLThen(url, function(res){
  464. // We were using pre instead of div to get monospace like <tt> or <code>
  465. // However since we are commonly reading the description, sans seems better.
  466. var displayDiv = document.createElement("div");
  467. displayDiv.style.fontSize = '0.8em';
  468. displayDiv.style.whiteSpace = "pre-wrap";
  469. var displayCode = document.createElement("pre");
  470. displayCode.textContent = res.responseText;
  471. displayCode.style.maxHeight = "100%";
  472. displayCode.style.overflow = "auto";
  473. displayDiv.appendChild(displayCode);
  474. while (frame.firstChild) {
  475. frame.removeChild(frame.firstChild);
  476. }
  477. frame.appendChild(displayDiv);
  478. if (typeof Rainbow != null) {
  479. /*
  480. // Works fine in Chrome, but in Firefox it causes Rainbow to fail with "too much recursion".
  481. Rainbow.extend('javascript', [
  482. {
  483. 'name': 'importantcomment',
  484. 'pattern': /(\/\/|\#) @(name|description|include) [\s\S]*?$/gm
  485. },
  486. ], false);
  487. */
  488. setTimeout(function(){
  489. displayCode.setAttribute('data-language', "javascript");
  490. displayCode.style.fontSize = '100%';
  491. Rainbow.color(displayCode.parentNode, function(){
  492. console.log("Rainbow finished.");
  493. });
  494. },50);
  495. }
  496. // frame.addEventListener("mouseout",cleanup,false);
  497. // newLink.title = res.responseText;
  498. var lines = res.responseText.split("\n");
  499. for (var i=0;i<lines.length;i++) {
  500. var line = lines[i];
  501. if (line.match(/@description\s/)) {
  502. var descr = line.replace(/.*@description\s*/,'');
  503. newLink.title = descr;
  504. break;
  505. }
  506. }
  507. }, function(res){
  508. reportToFrame("Failed to load "+url+": HTTP "+res.status);
  509. });
  510.  
  511. /*
  512. frame.style.position = 'fixed';
  513. frame.style.top = evt.clientY+4+'px';
  514. frame.style.left = evt.clientX+4+'px';
  515. */
  516. // frame.style.position = 'absolute';
  517. // frame.style.top = evt.layerY+12+'px';
  518. // frame.style.left = evt.layerX+12+'px';
  519. // frame.style.top = evt.layerY - window.innerHeight*35/100 + 'px';
  520. // frame.style.left = evt.layerX + 64 + 'px';
  521. // frame.style.width = "70%";
  522. // frame.style.height = "70%";
  523. frame.style.position = 'fixed';
  524. frame.style.right = '2%';
  525. frame.style.width = '50%';
  526. frame.style.top = '10%';
  527. frame.style.height = '80%';
  528. frame.style.backgroundColor = 'white';
  529. frame.style.color = 'black';
  530. frame.style.padding = '8px';
  531. frame.style.border = '2px solid #555555';
  532. document.body.appendChild(frame);
  533.  
  534. reportToFrame("Loading...");
  535.  
  536. }
  537.  
  538. function buildSourceViewer(link) {
  539. var newLink = document.createElement("A");
  540. // newLink.href = '#';
  541. newLink.textContent = "Source";
  542.  
  543. newLink.addEventListener('click',function(e) {
  544. loadSourceViewer(link.href,newLink,e);
  545. },false);
  546.  
  547. // TODO: Problem with .user.js files and Chrome:
  548. // In Chrome, opens an empty iframe then the statusbar says it wants to install an extension.
  549. // For Chrome we could try: frame.src = "view-source:"+...;
  550. var extra = document.createElement("span");
  551. extra.appendChild(document.createTextNode("["));
  552. extra.appendChild(newLink);
  553. extra.appendChild(document.createTextNode("]"));
  554. extra.style.paddingLeft = '8px';
  555.  
  556. // link.parentNode.insertBefore(extra,link.nextSibling);
  557. return extra;
  558. }
  559.  
  560. function popupSourceOnHover(link) {
  561. var hoverTimer = null;
  562. function startHover(evt) {
  563. stopHover(evt);
  564. hoverTimer = setTimeout(function(){
  565. loadSourceViewer(link.href, link, evt);
  566. stopHover(evt);
  567. // link.removeEventListener("mouseover",startHover,false);
  568. // link.removeEventListener("mouseout",stopHover,false);
  569. },1500);
  570. }
  571. function stopHover(evt) {
  572. clearTimeout(hoverTimer);
  573. hoverTimer = null;
  574. }
  575. link.addEventListener("mouseover",startHover,false);
  576. link.addEventListener("mouseout",stopHover,false);
  577. // If they click on it before waiting to hover, they probably don't want the popup:
  578. link.addEventListener("click",stopHover,false);
  579. }
  580.  
  581. function buildLiveUserscript(link) {
  582. //// This isn't working any more. data:// lost its power circa 2006 due to abuse.
  583. //// Create a clickable link that returns a sort-of file to the browser using the "data:" protocol.
  584. //// That file would be a new userscript for installation.
  585. //// We can generate the contents of this new userscript at run-time.
  586. //// The current one we generate runs (no @includes), and loads the latest userscript from its website via script injection.
  587. /* DISABLED
  588. // BUG: data:{...}.user.js does not interest my Chromium
  589. var name = getNameFromFilename(link.href)+" Live!";
  590. var name = "Install LiveLoader";
  591. var whatToRun = '(function(){\n'
  592. + ' var ns = document.createElement("script");\n'
  593. + ' ns.src = "' + encodeURI(getCanonicalUrl(link.href)) + '";\n'
  594. + ' document.getElementsByTagName("head")[0].appendChild(ns);\n'
  595. + '})();\n';
  596. var newLink = document.createElement("a");
  597. newLink.textContent = name;
  598. newLink.href = "data:text/javascript;charset=utf-8,"
  599. + "// ==UserScript==%0A"
  600. + "// @namespace LiveLoader%0A"
  601. + "// @name " + name + " LIVE%0A"
  602. + "// @description Loads " +name+ " userscript live from " + link.href + "%0A"
  603. + "// ==/UserScript==%0A"
  604. + "%0A"
  605. + encodeURIComponent(whatToRun) + "%0A"
  606. + "//.user.js";
  607. var extra = document.createElement("span");
  608. extra.appendChild(document.createTextNode("["));
  609. extra.appendChild(newLink);
  610. extra.appendChild(document.createTextNode("]"));
  611. extra.style.paddingLeft = '8px';
  612. link.parentNode.insertBefore(extra,link.nextSibling);
  613. */
  614. return document.createTextNode("");
  615. }
  616.  
  617. function getCanonicalUrl(url) {
  618. if (url.substring(0,1)=="/") {
  619. url = document.location.protocol + "://" + document.location.domain + "/" + url;
  620. }
  621. if (!url.match("://")) {
  622. url = document.location.href.match("^[^?]*/") + url;
  623. }
  624. return url;
  625. }
  626.  
  627. function getNameFromFilename(href) {
  628. var isUserscript = href.match(/\.user\.js$/);
  629. // Remove any leading folders and trailing ".user.js"
  630. var name = href.match(/[^\/]*$/)[0].replace(/\.user\.js$/,'');
  631.  
  632. name = decodeURIComponent(name);
  633.  
  634. // The scripts on userscripts.org do not have their name in the filename,
  635. // but we can get the name from the page title!
  636. if (document.location.host=="userscripts.org" && document.location.pathname=="/scripts/show/"+name) {
  637. var scriptID = name;
  638. name = document.title.replace(/ for Greasemonkey/,'');
  639. // Optionally, include id in name:
  640. name += " ("+scriptID+")";
  641. }
  642.  
  643. if (isUserscript) {
  644. var words = name.split("_");
  645. for (var i=0;i<words.length;i++) {
  646. if (words[i].length) {
  647. var c = words[i].charCodeAt(0);
  648. if (c>=97 && c<=122) {
  649. c = 65 + (c - 97);
  650. words[i] = String.fromCharCode(c) + words[i].substring(1);
  651. }
  652. }
  653. }
  654. name = words.join(" ");
  655. } else {
  656. // It's just a Javascript file
  657. name = "Load "+name;
  658. }
  659. return name;
  660. }
  661.  
  662. var links = document.getElementsByTagName("A");
  663. //// We used to process backwards (for less height recalculation).
  664. //// But this was messing up maxStaticsToRequest.
  665. //// But now backwards processing is restored, to avoid producing multiple bookmarks!
  666. for (var i=links.length;i--;) {
  667. //for (var i=0;i<links.length;i++) {
  668. var link = links[i];
  669.  
  670. if (link.getAttribute('data-make-bookmarklet') === 'false') {
  671. continue;
  672. }
  673.  
  674. // If we see a direct link to a user script, create buttons for it.
  675. if (link.href.match(/\.js$/)) { // \.user\.js
  676. var where = link;
  677. function insert(newElem) {
  678. where.parentNode.insertBefore(newElem,where.nextSibling);
  679. where = newElem;
  680. }
  681. insert(buildLiveBookmarklet(link));
  682. insert(buildStaticBookmarklet(link));
  683. insert(buildLiveUserscript(link));
  684. insert(buildSourceViewer(link));
  685. }
  686.  
  687. // If the current page looks like a Greasemonkey Userscript Folder, then
  688. // create an installer for every subfolder (assuming a script is inside it).
  689. if (document.location.pathname.match(/\/(gm_scripts|userscripts)\//)) {
  690. if (link.href.match(/\/$/) && link.textContent!=="Parent Directory") {
  691. addQuickInstall(link);
  692. }
  693. }
  694.  
  695. }
  696.  
  697. /*
  698. var promise(getURLThen,url) {
  699. var handler;
  700.  
  701. getURLThen(url,handlerFn,handlerFn);
  702.  
  703. function handlerFn(res) {
  704. var source = res.responseText;
  705. if (handler) {
  706. handler(source);
  707. } else {
  708. reportError("No handler set for: "+
  709. }
  710. }
  711.  
  712. return {
  713. then: function(handleResponse){
  714. handler = handleResponse;
  715. }
  716. };
  717. }
  718. */
  719.