Workflowier

User Script for Workflowy.com that adds some extra features.

当前为 2016-05-11 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Workflowier
  3. // @namespace Workflowier
  4. // @include https://workflowy.com/*
  5. // @author Nick Busey
  6. // @grant none
  7. // @description User Script for Workflowy.com that adds some extra features.
  8. // @version 0.0.2.3
  9. // @license MIT
  10. // @homepageURL http://workflowier.com/
  11. // ==/UserScript==
  12.  
  13. // a function that loads jQuery and calls a callback function when jQuery has finished loading
  14. function addJQuery(callback) {
  15. var script = document.createElement("script");
  16. script.setAttribute("src", "//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js");
  17. script.addEventListener('load', function() {
  18. var script = document.createElement("script");
  19. script.textContent = "window.jQ=jQuery.noConflict(true);(" + callback.toString() + ")();";
  20. document.body.appendChild(script);
  21. }, false);
  22. document.body.appendChild(script);
  23. }
  24.  
  25. searching = false;
  26.  
  27. // the guts of this userscript
  28. function main() {
  29. // Note, jQ replaces jQ to avoid conflicts.
  30. // Insert recent links
  31. var recentLinks = "<div class='menu-options' id='recentLinksMenu'>"+
  32. "<div class='button'><div class='topBarButtonTextContainer'><a href='#' id='recentLink_1wk'>This Week</a></div></div>"+
  33. "<div class='button'><div class='topBarButtonTextContainer'><a href='#' class='button' id='recentLink_24hrs'>Today</a></div></div>"+
  34. "<div class='button'><div class='topBarButtonTextContainer'><a href='#' class='button' id='recentLink_1hr'>Last Hour</a></div></div>"+
  35. "</div>";
  36. jQ('#savedViewHUDButton').after("<div class='menuButton button'><div class='topBarButtonTextContainer'><a href='#' id='showRecentLinks'>Recent</a></div></div>"+recentLinks);
  37.  
  38. jQ('#showRecentLinks').click(function(e) {
  39. e.preventDefault();
  40. jQ('#recentLinksMenu').slideToggle();
  41. });
  42.  
  43. jQ('#recentLink_1wk').click(function(e) {
  44. e.preventDefault();
  45. if (jQ('#searchBox').val()=='last-changed:7d') {
  46. search.searchProjectTree('');
  47. } else {
  48. search.searchProjectTree('last-changed:7d');
  49. }
  50. });
  51. jQ('#recentLink_24hrs').click(function(e) {
  52. e.preventDefault();
  53. if (jQ('#searchBox').val()=='last-changed:1d') {
  54. search.searchProjectTree('');
  55. } else {
  56. search.searchProjectTree('last-changed:1d');
  57. }
  58. });
  59. jQ('#recentLink_1hr').click(function(e) {
  60. e.preventDefault();
  61. if (jQ('#searchBox').val()=='last-changed:1h') {
  62. search.searchProjectTree('');
  63. } else {
  64. search.searchProjectTree('last-changed:1h');
  65. }
  66. });
  67.  
  68. var generateTagList = function() {
  69. // Generate list of all hashtags
  70. var tags = $('.contentTagText');
  71. var tagObjs = {};
  72. tags.each(function(ii, obj) {
  73. var tag = jQ(obj).text().toLowerCase();
  74.  
  75. var tagObj = tagObjs[tag];
  76. if (!tagObj) {
  77. tagObj = {'count':1};
  78. } else {
  79. tagObj.count++;
  80. }
  81. tagObjs[tag] = tagObj;
  82. });
  83. var tagObjsArray = [];
  84. for (var tag in tagObjs) {
  85. var tagObj = tagObjs[tag];
  86. tagObj.tag = tag;
  87. tagObjsArray.push(tagObj);
  88. }
  89. return tagObjsArray.sort(function (a, b) {
  90. return b.count - a.count;
  91. });
  92. };
  93.  
  94. var generateTags = function() {
  95. var currentSearch = jQ('#searchBox').val();
  96. // First let's delete the existing tags index, or else it will count those and old tags are never removed.
  97. search.searchProjectTree('#wf-tag-list');
  98. $('.project.matches:last .notes .content').text('');
  99. $('.project.matches:last .content').trigger('blur');
  100. // Now find existing tags.
  101. search.searchProjectTree('#');
  102. var allTags = generateTagList();
  103. // Now find which of those are completed
  104. search.searchProjectTree('# is:complete');
  105. var completedTags = generateTagList();
  106.  
  107. // Store the list of tags
  108. updateTagsNote(allTags);
  109.  
  110. // Update the menu
  111. var tagLinkOutput = '';
  112. for (var ii in allTags) {
  113. var count = allTags[ii]['count'];
  114. var tag = allTags[ii]['tag'];
  115. var completed = completedTags.filter(function (obj) {
  116. return obj.tag === tag;
  117. });
  118. var completed_count = (completed[0]) ? completed[0].count : 0;
  119. tagLinkOutput += "<a href='/#/"+tag+"?q=%23"+tag+"' title='"+Math.round(100*(completed_count/count))+"% "+completed_count+"/"+count+" complete.'><strong>"+count+"</strong> #"+tag+"</a>";
  120. }
  121. $('#tagsMenu').html(tagLinkOutput);
  122. search.searchProjectTree(currentSearch);
  123.  
  124. };
  125.  
  126. var generateTagsMenu = function () {
  127. // Ensure the search is ready. This will throw an exception if not.
  128. var currentSearch = jQ('#searchBox').val();
  129. search.searchProjectTree('#wf-tag-list');
  130. search.searchProjectTree(currentSearch);
  131.  
  132. generateTags();
  133. jQ('#savedViewHUDButton').after("<div class='button menuButton'><div class='topBarButtonTextContainer'><a href='#' class='button' id='openTags'>View Tags</a></div></div><div class='menu-options' id='tagsMenu'></div>");
  134. jQ('#openTags').on('click',function(e) {
  135. e.preventDefault();
  136. // If we're showing the tags menu, regenerate the tags list. Don't do it on hide.
  137. if ($('#tagsMenu:visible').length < 1) {
  138. generateTags();
  139. }
  140. jQ('#tagsMenu').slideToggle();
  141. });
  142. };
  143.  
  144. var updateTagsNote = function(tagArray) {
  145. window.location.hash='';
  146. search.searchProjectTree('#wf-tag-list');
  147. var tagList = '';
  148. for (var ii in tagArray) {
  149. var count = tagArray[ii]['count'];
  150. var tag = tagArray[ii]['tag'];
  151. tagList += count+" #"+tag+" - ";
  152. }
  153.  
  154. $('.project.matches:last .notes .content').text('View Full List: '+tagList);
  155. $('.project.matches:last .content').trigger('blur');
  156. };
  157.  
  158. var attemptTags = function() {
  159. setTimeout(function() {
  160. try {
  161. generateTagsMenu();
  162. } catch(e) {
  163. attemptTags();
  164. }
  165. },500);
  166. };
  167.  
  168. attemptTags();
  169.  
  170. // Add -rand functionality
  171. jQ(window).on('hashchange',function(e) {
  172. if (searching) {
  173. return false;
  174. }
  175.  
  176. var query = jQ('#searchBox').val();
  177. var needle=/(%23\w*-rand)+/;
  178. var match = window.location.href.match(needle);
  179. if (match) {
  180. // A tag with -rand on the end has been clicked. Locate another.
  181. searching = true;
  182. var tag = match[0]; //matches "2 chapters"
  183. tag = "#"+tag.slice(3);
  184. window.location.href='/#';
  185. search.searchProjectTree(tag);
  186. var target = null;
  187. var count = 0;
  188. var tags = $('.contentMatch');
  189. var random = $(tags[Math.floor(Math.random()*tags.length)])[0];
  190. var parent = jQ(random).parents('.name').find('a').first();
  191. var href = jQ(parent).attr('href');
  192. window.location.href = href;
  193. setTimeout(function() {
  194. searching = false;
  195. },100);
  196. }
  197. });
  198.  
  199. // Add image popups
  200. // jQ('a').live('mouseenter',function (e) {
  201. // console.log(e);
  202. // });
  203.  
  204. // Add styles
  205. jQ('body').append("<style>"+
  206. "#tagsMenu{ height:300px; overflow:scroll; max-width: 250px; right: 140px; }"+
  207. "#tagsMenu a { margin: 0 5px; display: block; }"+
  208. "#recentLinksMenu{ right:400px; }"+
  209. ".menuButton{ display: block; color: white; margin-left: -1px; padding: 8px 1em; font-size: 13px; text-align: center; float: right; border-bottom: none; border-left: 1px solid #111; border-right: 1px solid #111; border-radius: 0; background-color: #555; position: relative;}"+
  210. "</style>");
  211.  
  212. // Add coloring styles (ala Paintly, stolen from: https://userstyles.org/styles/125832/re-workflowy-re-painter)
  213. setInterval(function() {
  214. $('.content').css('background-color','');
  215. var colors = {
  216. 'red':'#FFB5B5',
  217. 'orange':'#FFD8B5',
  218. 'yellow':'#FFFAB5',
  219. 'lime':'#E1FFB5',
  220. 'olive':'#B5FFC9',
  221. 'green':'#CCFFB5',
  222. 'teal':'#B5FFD7',
  223. 'aquea':'#B5FFFC',
  224. 'blu':'#B5E8FF',
  225. 'navy':'#B9B5FF',
  226. 'fuchia':'#F1B5FF',
  227. 'purple':'#D3B5FF',
  228. 'maroon':'#C08F8F',
  229. 'silver':'silver',
  230. 'gray':'gray',
  231. 'black':'black',
  232. 'white':'white'
  233. };
  234. for (var ii in colors) {
  235. var color = colors[ii];
  236. $('.content:contains("#'+ii+'")').css('background-color',color);
  237. }
  238. },500);
  239. }
  240.  
  241. // load jQuery and execute the main function
  242. addJQuery(main);