DuckDuckGo Extended [shorter version] (Mobile)

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

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