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-01-24 提交的版本,查看 最新版本

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