DuckDuckGo Extended

Extends DDG by adding a customizable list of additional search engines for making fast searches from other engines.

目前为 2015-01-24 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name DuckDuckGo Extended
  3. // @description Extends DDG by adding a customizable list of additional search engines for making fast searches from other engines.
  4. // @namespace userscripts.org/users/439657
  5. // @homepage http://userscripts.org/scripts/show/129505
  6. // @icon http://s3.amazonaws.com/uso_ss/icon/129505/large.png?1368599692
  7. // @match *://duckduckgo.com/*
  8. // @exclude *://duckduckgo.com/post2.html
  9. // @match http://mycroftproject.com/*
  10. // @grant GM_addStyle
  11. // @grant GM_getValue
  12. // @grant GM_setValue
  13. // @grant GM_xmlhttpRequest
  14. // @version 2.0.4
  15. // @author tumpio
  16. // ==/UserScript==
  17.  
  18. var ddg_e = {
  19.  
  20. list: document.createElement("ol"),
  21. engines: [],
  22.  
  23. default: "Google==http://www.google.com/search?q={searchTerms}\
  24. ;;Images==http://www.bing.com/images/search?q={searchTerms}&FORM=BIFD\
  25. ;;Wiki==http://en.wikipedia.org/w/index.php?title=Special%3ASearch&profile=default&search={searchTerms}\
  26. ;;Maps==http://maps.google.com/maps?q={searchTerms}\
  27. ;;YouTube==http://www.youtube.com/results?search_query={searchTerms}&aq=f\
  28. ;;IMDb==http://www.imdb.com/find?s=all&q={searchTerms}\
  29. ;;Music==http://www.musicsmasher.net/#{searchTerms}\
  30. ;;Facebook==http://www.facebook.com/search.php?q={searchTerms}\
  31. ;;Google+==https://plus.google.com/s/{searchTerms}\
  32. ;;twitter==https://twitter.com/#!/search/realtime/{searchTerms}\
  33. ;;Amazon==http://www.amazon.com/gp/search?ie=UTF8&keywords={searchTerms}\
  34. ;;Torrents==http://scrapetorrent.com/Search/index.php?search={searchTerms}&sort=seed&fz=&zs=&cat=\
  35. ;;filesTube==http://www.filestube.com/search.html?q={searchTerms}\
  36. ;;Translate==http://translate.google.com/translate_t#auto|en|{searchTerms}",
  37.  
  38. style: "#ddg_extented { float:left; width:100%; position:relative; top:0px; z-index:999 }\
  39. #ddg_extented ol { clear:left; float:right; list-style:none; position:relative; right:50%; text-align:center; margin:0; padding:0 }\
  40. #ddg_extented li { display:inline; list-style:none; position:relative; left:50%; box-shadow:0 2px 5px 0 #888; margin:0; padding:0; background:#b60002 }\
  41. #ddg_extented li:first-child { border-bottom-left-radius: 10px }\
  42. #ddg_extented li:last-child { border-bottom-right-radius: 10px }\
  43. #ddg_extented li a:link,li a:visited { text-decoration:none; color:#fff; font-size:1.0em; font-weight:700; padding:0 9px }\
  44. #ddg_extented li a:hover { color:#91C5EE }\
  45. #ddg_extented li:hover { box-shadow:0 3px 3px 0 #888 }\
  46. #ddg_extented li.disabled a {pointer-events: none }\
  47. body #header_wrapper #header #header_content_wrapper #header_content #header_button_wrapper #header_button #header_button_menu_wrapper #header_button_menu li.disabled {display: none!important;}\
  48. #ddg_e_save a { background: #88FF61!important }\
  49. #ddg_e_cancel a { background: #FF8861!important }",
  50.  
  51. get: function() {
  52. var e = GM_getValue("engines", this.
  53. default).split(";;");
  54. var a = [];
  55. for (var i = 0; i < e.length; i++)
  56. a.push(e[i].split("=="));
  57. this.length = a.length;
  58. return a;
  59. },
  60.  
  61. set: function() {
  62. var e = "";
  63. for (var i = 0; i < this.engines.length; i++) {
  64. e += ";;" + this.engines[i].textContent + "==" + this.engines[i].firstChild.getAttribute("data-engine");
  65. }
  66. GM_setValue("engines", e.substr(2));
  67. },
  68.  
  69. newList: function() {
  70. var l = "";
  71. var e = this.get();
  72. for (var i = 0; i < e.length; i++)
  73. l += '<li draggable="true" value=' + (i + 1) + '"><a data-engine="' + e[i][1] + '"href="#' + e[i][1] + '">' + e[i][0] + "</a></li>";
  74. this.list.innerHTML = l;
  75. this.engines = this.list.getElementsByTagName("li");
  76. // Joey added this so that clicks work again.
  77. // I don't know how they worked before.
  78. // Were they handled by DDG itself?
  79. // Or is it somewhere in this script that broke in Greasemonkey?
  80. for (var i = 0; i < this.engines.length; i++)
  81. this.engines[i].firstChild.onclick = onHashChange;
  82. },
  83.  
  84. append: function(h) {
  85. var e = document.createElement("div");
  86. var b = h.style.background;
  87. if (b !== "")
  88. this.style += "#ddg_extented li { background:" + b + "!important }";
  89. GM_addStyle(this.style);
  90. e.setAttribute("id", "ddg_extented");
  91. e.appendChild(this.list);
  92. this.newList();
  93. h.appendChild(e);
  94. }
  95. };
  96.  
  97. var options = {
  98.  
  99. size: 7,
  100. list: [],
  101. strings: [
  102. ["Find new", "Find and add new search engines from MyCroft"],
  103. ["Reorder", "Drag and drop search engines"],
  104. ["Rename", "Click to rename search engines"],
  105. ["Remove", "Click to remove search engines"],
  106. ["Restore all", "Restores all default search engines"],
  107. ["Save", "Saves modified search engines"],
  108. ["Cancel", "Cancels all recent modifications"]
  109. ],
  110.  
  111. create: function(m) {
  112. m.innerHTML += '<li class="header_button_menu_header">DDG Extended</li>';
  113. for (var i = 0, li; i < this.size; i++) {
  114. li = document.createElement("li");
  115. li.className = "enabled";
  116. li.innerHTML = '<a tabindex="-1" title="' + this.strings[i][1] + '">' + this.strings[i][0] + "</a>";
  117. this.list.push(li);
  118. m.appendChild(this.list[i]);
  119. }
  120. this.list[5].className = "disabled";
  121. this.list[6].className = "disabled";
  122. this.list[5].id = "ddg_e_save";
  123. this.list[6].id = "ddg_e_cancel";
  124. this.list[0].addEventListener("click", newEngine, false);
  125. this.list[1].addEventListener("click", enableDrag, false);
  126. this.list[2].addEventListener("click", enableRename, false);
  127. this.list[3].addEventListener("click", enableRemove, false);
  128. this.list[4].addEventListener("click", restoreEngines, false);
  129. this.list[5].addEventListener("click", saveActions, false);
  130. this.list[6].addEventListener("click", cancelActions, false);
  131. },
  132.  
  133. toggleClass: function() {
  134. for (var i = 0; i < this.list.length; i++) {
  135. if (this.list[i].className === "disabled")
  136. this.list[i].className = "enabled";
  137. else
  138. this.list[i].className = "disabled";
  139. }
  140. }
  141.  
  142. };
  143.  
  144. var mycroft = {
  145.  
  146. plugins: null,
  147.  
  148. addLinks: function(p) {
  149.  
  150. if (p) {
  151. this.plugins = document.evaluate('//a[@href="/jsreq.html"]',
  152. p, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
  153. var reviews = document.evaluate('//a[.="[Review]"]',
  154. p, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
  155. var addLink = document.createElement("a");
  156. addLink.setAttribute("href", "javascript:void(0)");
  157. addLink.setAttribute("style", "margin-left:5px; color:#000099");
  158. addLink.innerHTML = "[Add to DDG]";
  159. for (var i = 0, tmp; i < reviews.snapshotLength; i++) {
  160. tmp = addLink.cloneNode(true);
  161. tmp.setAttribute("data-ind", i);
  162. tmp.addEventListener("click", this.addNewEngine, false);
  163. reviews.snapshotItem(i).parentNode.insertBefore(tmp, reviews.snapshotItem(i).nextSibling);
  164. }
  165. }
  166.  
  167. },
  168.  
  169. addNewEngine: function() {
  170. var current = GM_getValue("engines", ddg_e.
  171. default);
  172. var i = this.getAttribute("data-ind");
  173. var name = mycroft.plugins.snapshotItem(i).innerHTML.split(" (")[0].split(" -")[0];
  174. var newEngine = mycroft.plugins.snapshotItem(i).getAttribute("onClick").split("'")[1];
  175. var newName = prompt("This engine will be added to DDG Extended.\nGive a name or cancel.", name);
  176. if (newName && newName.length > 0) {
  177. this.innerHTML = "[Added]";
  178. this.removeEventListener("click", this.addNewEngine, false);
  179. this.style.color = "#009900";
  180. this.removeAttribute("href");
  181.  
  182. GM_xmlhttpRequest({
  183. method: "GET",
  184. url: "http://mycroftproject.com/externalos.php/" + newEngine + ".xml",
  185. onload: function(response) {
  186. var responseXML = null;
  187. // Inject responseXML into existing Object (only appropriate for XML content).
  188. if (!response.responseXML) {
  189. responseXML = new DOMParser()
  190. .parseFromString(response.responseText, "text/xml");
  191. }
  192. var engine = responseXML.getElementsByTagName("Url");
  193. if (engine.length > 0 && engine[0].getAttribute("template"))
  194. GM_setValue("engines", current + ";;" + newName + "==" + engine[0].getAttribute("template"));
  195. }
  196. });
  197. }
  198. }
  199. };
  200.  
  201. // FUNCTIONS
  202. function onHashChange() {
  203. if (window.location.hash.indexOf("{searchTerms}") !== -1)
  204. window.location.hash = "";
  205. window.addEventListener("hashchange", function() {
  206. if (window.location.hash.indexOf("{searchTerms}") !== -1)
  207. window.location.href = window.location.hash.substr(1).replace("{searchTerms}", escape(document.getElementById("search_form_input").value));
  208. }, false);
  209. }
  210.  
  211. function onColorChange(h, c) {
  212. if (c !== null) {
  213. c.addEventListener("change", function() {
  214. for (var i = 0; i < ddg_e.engines.length; i++)
  215. ddg_e.engines[i].style.background = h.style.background;
  216. }, false);
  217. }
  218. }
  219.  
  220. function restoreEngines() {
  221. if (confirm("Do you want to restore the default search engines?")) {
  222. GM_setValue("engines", ddg_e.
  223. default);
  224. ddg_e.newList();
  225. }
  226. }
  227.  
  228. function saveActions() {
  229. disableEvents();
  230. ddg_e.set();
  231. }
  232.  
  233. function cancelActions() {
  234. disableEvents();
  235. ddg_e.newList();
  236. }
  237.  
  238. function enableDrag() {
  239. options.toggleClass();
  240. for (var i = 0; i < ddg_e.engines.length; i++) {
  241. ddg_e.engines[i].style.cursor = "move";
  242. ddg_e.engines[i].className = "disabled";
  243. ddg_e.engines[i].addEventListener("dragstart", handleDragStart, false);
  244. ddg_e.engines[i].addEventListener("dragover", handleDragOver, false);
  245. ddg_e.engines[i].addEventListener("drop", handleDrop, false);
  246. }
  247. }
  248.  
  249. function enableRemove() {
  250. options.toggleClass();
  251. for (var i = 0; i < ddg_e.engines.length; i++) {
  252. ddg_e.engines[i].style.cursor = "crosshair";
  253. ddg_e.engines[i].className = "disabled";
  254. ddg_e.engines[i].addEventListener("click", remove, false);
  255. }
  256. }
  257.  
  258. function enableRename() {
  259. options.toggleClass();
  260. for (var i = 0; i < ddg_e.engines.length; i++) {
  261. ddg_e.engines[i].style.cursor = "text";
  262. ddg_e.engines[i].className = "disabled";
  263. ddg_e.engines[i].addEventListener("click", rename, false);
  264. }
  265. }
  266.  
  267. function disableEvents() {
  268. options.toggleClass();
  269. for (var i = 0; i < ddg_e.engines.length; i++) {
  270. ddg_e.engines[i].style.cursor = "auto";
  271. ddg_e.engines[i].removeAttribute("class");
  272. ddg_e.engines[i].removeEventListener("dragstart", handleDragStart, false);
  273. ddg_e.engines[i].removeEventListener("dragover", handleDragOver, false);
  274. ddg_e.engines[i].removeEventListener("drop", handleDrop, false);
  275. ddg_e.engines[i].removeEventListener("click", remove, false);
  276. ddg_e.engines[i].removeEventListener("click", rename, false);
  277. }
  278. }
  279.  
  280. function remove() {
  281. this.parentNode.removeChild(this);
  282. }
  283.  
  284. function rename() {
  285. var n = prompt("Rename search engine", this.textContent);
  286. if (n && n.length > 0 && n.length < 20)
  287. this.firstChild.innerHTML = n;
  288. }
  289.  
  290. function newEngine() {
  291. var n = prompt("Search and add new search engines from MyCroft.\nYou can search by name and url.", "");
  292. if (n && n.length > 0)
  293. window.location.href = "http://mycroftproject.com/search-engines.html?name=" + n;
  294. }
  295.  
  296.  
  297. /* Pure javascript and html5 drag-and-drop for an ordered list
  298. * source: https://gist.github.com/robophilosopher/7520460
  299. */
  300. var dragSrcEl,drop_index,numLis,count,token1,token2;
  301. function handleDragStart(e) {
  302. dragSrcEl = this;
  303. e.dataTransfer.effectAllowed = "move";
  304. e.dataTransfer.setData("text/html", this.innerHTML);
  305. }
  306.  
  307. function handleDragOver(e) {
  308. if (e.preventDefault) e.preventDefault();
  309. return false;
  310. }
  311.  
  312. function handleDrop(e) {
  313. if (e.stopPropagation) e.stopPropagation();
  314. if (e.preventDefault) e.preventDefault();
  315. drop_index = this.value;
  316. numLis = parseInt(drop_index - dragSrcEl.value);
  317. count = Math.abs(numLis);
  318. if (dragSrcEl != this) {
  319. // swap data when premises are adjacent
  320. if (Math.abs(numLis) == 1) {
  321. dragSrcEl.innerHTML = this.innerHTML;
  322. this.innerHTML = e.dataTransfer.getData("text/html");
  323. }
  324. // propagate data from non-adjacent drops
  325. if (Math.abs(numLis) > 1) {
  326. token1 = this.innerHTML;
  327. this.innerHTML = e.dataTransfer.getData("text/html");
  328. // bring premise up list
  329. if (numLis < -1) {
  330. for (var i = drop_index, counter = 0; counter < count; counter++, i++) {
  331. token2 = ddg_e.engines[i].innerHTML;
  332. ddg_e.engines[i].innerHTML = token1;
  333. token1 = token2;
  334. }
  335. }
  336. // bring premise down list
  337. if (numLis > 1) {
  338. for (var i = drop_index - 1, counter = 0; counter < count; counter++, i--) {
  339. token2 = ddg_e.engines[i - 1].innerHTML;
  340. ddg_e.engines[i - 1].innerHTML = token1;
  341. token1 = token2;
  342. }
  343. }
  344. }
  345. }
  346. return false;
  347. }
  348.  
  349. // START
  350. if (window.location.href.indexOf("http://mycroftproject.com/") !== -1) {
  351. mycroft.addLinks(document.getElementById("plugins"));
  352. } else {
  353. var header = document.getElementById("header");
  354. if (header) {
  355. ddg_e.append(header);
  356. options.create(document.getElementById("header_button_menu"));
  357. onColorChange(header, document.getElementById("kj"));
  358. onHashChange();
  359. }
  360. }