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

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

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址