Workflowier

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

目前为 2016-04-10 提交的版本,查看 最新版本

  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.1.9
  9. // ==/UserScript==
  10.  
  11. // a function that loads jQuery and calls a callback function when jQuery has finished loading
  12. function addJQuery(callback) {
  13. var script = document.createElement("script");
  14. script.setAttribute("src", "//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js");
  15. script.addEventListener('load', function() {
  16. var script = document.createElement("script");
  17. script.textContent = "window.jQ=jQuery.noConflict(true);(" + callback.toString() + ")();";
  18. document.body.appendChild(script);
  19. }, false);
  20. document.body.appendChild(script);
  21. }
  22.  
  23. searching = false;
  24.  
  25. // the guts of this userscript
  26. function main() {
  27. // Note, jQ replaces jQ to avoid conflicts.
  28. // Insert recent links
  29. var recentLinks = "<div class='menu-options' id='recentLinksMenu'>"+
  30. "<div class='button'><div class='topBarButtonTextContainer'><a href='#' id='recentLink_1wk'>This Week</a></div></div>"+
  31. "<div class='button'><div class='topBarButtonTextContainer'><a href='#' class='button' id='recentLink_24hrs'>Today</a></div></div>"+
  32. "<div class='button'><div class='topBarButtonTextContainer'><a href='#' class='button' id='recentLink_1hr'>Last Hour</a></div></div>"+
  33. "</div>";
  34. jQ('#savedViewHUDButton').after("<div class='menuButton button'><div class='topBarButtonTextContainer'><a href='#' id='showRecentLinks'>Recent</a></div></div>"+recentLinks);
  35.  
  36. jQ('#showRecentLinks').click(function(e) {
  37. e.preventDefault();
  38. jQ('#recentLinksMenu').slideToggle();
  39. });
  40.  
  41. jQ('#recentLink_1wk').click(function(e) {
  42. e.preventDefault();
  43. if (jQ('#searchBox').val()=='last-changed:7d') {
  44. search.searchProjectTree('');
  45. } else {
  46. search.searchProjectTree('last-changed:7d');
  47. }
  48. });
  49. jQ('#recentLink_24hrs').click(function(e) {
  50. e.preventDefault();
  51. if (jQ('#searchBox').val()=='last-changed:1d') {
  52. search.searchProjectTree('');
  53. } else {
  54. search.searchProjectTree('last-changed:1d');
  55. }
  56. });
  57. jQ('#recentLink_1hr').click(function(e) {
  58. e.preventDefault();
  59. if (jQ('#searchBox').val()=='last-changed:1h') {
  60. search.searchProjectTree('');
  61. } else {
  62. search.searchProjectTree('last-changed:1h');
  63. }
  64. });
  65.  
  66. var generateTagList = function() {
  67. // Generate list of all hashtags
  68. var tags = $('.contentTagText');
  69. var tagObjs = {};
  70. tags.each(function(ii, obj) {
  71. // console.log(obj);
  72. // console.log(jQ(obj).text());
  73. var tag = jQ(obj).text().toLowerCase();
  74.  
  75. var tagObj = tagObjs[tag];
  76. // console.log(tag,' - ',tagObj);
  77. if (!tagObj) {
  78. // console.log("No tag object, make an empty one.")
  79. tagObj = {'count':1};
  80. } else {
  81. tagObj['count']++;
  82. }
  83. tagObjs[tag] = tagObj;
  84. });
  85. // console.log(tagObjs);
  86. var tagObjsArray = [];
  87. for (var tag in tagObjs) {
  88. var tagObj = tagObjs[tag];
  89. tagObj['tag'] = tag;
  90. // console.log(tag,tagObj);
  91. tagObjsArray.push(tagObj);
  92. }
  93. return tagObjsArray.sort(function (a, b) {
  94. return b.count - a.count;
  95. });
  96. }
  97.  
  98. var generateTags = function() {
  99. var currentSearch = jQ('#searchBox').val();
  100. // First let's delete the existing tags index, or else it will count those and old tags are never removed.
  101. search.searchProjectTree('#wf-tag-list');
  102. $('.project.matches:last .notes .content').text('');
  103. $('.project.matches:last .content').trigger('blur');
  104. // Now find existing tags.
  105. search.searchProjectTree('#');
  106. var allTags = generateTagList();
  107. // Now find which of those are completed
  108. search.searchProjectTree('# is:complete');
  109. var completedTags = generateTagList();
  110.  
  111. // Store the list of tags
  112. updateTagsNote(allTags);
  113.  
  114. // Update the menu
  115. var tagLinkOutput = '';
  116. for (var ii in allTags) {
  117. var count = allTags[ii]['count'];
  118. var tag = allTags[ii]['tag'];
  119. var completed = completedTags.filter(function (obj) {
  120. return obj.tag === tag;
  121. });
  122. console.log(completed[0]);
  123. var completed_count = (completed[0]) ? completed[0].count : 0;
  124. console.log (completed_count);
  125. tagLinkOutput += "<a href='/#/"+tag+"?q=%23"+tag+"' title='"+Math.round(100*(completed_count/count))+"% "+completed_count+"/"+count+" complete.'><strong>"+count+"</strong> #"+tag+"</a>";
  126. }
  127. // console.log(tagLinkOutput);
  128. $('#tagsMenu').html(tagLinkOutput);
  129. search.searchProjectTree(currentSearch);
  130.  
  131. };
  132.  
  133. var generateTagsMenu = function () {
  134. // Ensure the search is ready. This will throw an exception if not.
  135. var currentSearch = jQ('#searchBox').val();
  136. search.searchProjectTree('#wf-tag-list');
  137. search.searchProjectTree(currentSearch);
  138.  
  139. generateTags();
  140. 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>");
  141. jQ('#openTags').on('click',function(e) {
  142. e.preventDefault();
  143. // If we're showing the tags menu, regenerate the tags list. Don't do it on hide.
  144. if ($('#tagsMenu:visible').length < 1) {
  145. generateTags();
  146. }
  147. jQ('#tagsMenu').slideToggle();
  148. });
  149. }
  150.  
  151. var updateTagsNote = function(tagArray) {
  152. window.location.hash='';
  153. search.searchProjectTree('#wf-tag-list');
  154. var tagList = '';
  155. for (var ii in tagArray) {
  156. var count = tagArray[ii]['count'];
  157. var tag = tagArray[ii]['tag'];
  158. tagList += count+" #"+tag+" - ";
  159. }
  160.  
  161. $('.project.matches:last .notes .content').text('View Full List: '+tagList);
  162. $('.project.matches:last .content').trigger('blur');
  163. };
  164.  
  165. var attemptTags = function() {
  166. setTimeout(function() {
  167. try {
  168. generateTagsMenu();
  169. } catch(e) {
  170. attemptTags();
  171. }
  172. },500);
  173. };
  174.  
  175. attemptTags();
  176.  
  177. // Add -rand functionality
  178. jQ(window).on('hashchange',function(e) {
  179. if (searching) {
  180. return false;
  181. }
  182.  
  183. var query = jQ('#searchBox').val();
  184. var needle=/(%23\w*-rand)+/;
  185. var match = window.location.href.match(needle);
  186. if (match) {
  187. // A tag with -rand on the end has been clicked. Locate another.
  188. searching = true;
  189. var tag = match[0]; //matches "2 chapters"
  190. tag = "#"+tag.slice(3);
  191. window.location.href='/#';
  192. search.searchProjectTree(tag);
  193. var target = null;
  194. var count = 0;
  195. var tags = $('.contentMatch');
  196. var random = $(tags[Math.floor(Math.random()*tags.length)])[0];
  197. var parent = jQ(random).parents('.name').find('a').first();
  198. var href = jQ(parent).attr('href');
  199. window.location.href = href;
  200. setTimeout(function() {
  201. searching = false;
  202. },100);
  203. }
  204. });
  205.  
  206.  
  207. // Add styles
  208. jQ('body').append("<style>"+
  209. "#tagsMenu{max-width: 250px; right: 140px;}"+
  210. "#tagsMenu a {margin: 0 5px; display: block;}"+
  211. "#recentLinksMenu{right:400px}"+
  212. ".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;}"+
  213. "</style>");
  214. }
  215.  
  216. // load jQuery and execute the main function
  217. addJQuery(main);