// ==UserScript== // @name Workflowier // @namespace Workflowier // @include https://workflowy.com/* // @author Nick Busey // @grant none // @description User Script for Workflowy.com that adds some extra features. // @version 0.2 // @license MIT // @homepageURL http://workflowier.com/ // @downloadURL https://update.greasyfork.icu/scripts/18496/Workflowier.user.js // @updateURL https://update.greasyfork.icu/scripts/18496/Workflowier.meta.js // ==/UserScript== // a function that loads jQuery and calls a callback function when jQuery has finished loading function addJQuery(callback) { var script = document.createElement("script"); script.setAttribute("src", "//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"); script.addEventListener('load', function() { var script = document.createElement("script"); script.textContent = "window.jQ=jQuery.noConflict(true);(" + callback.toString() + ")();"; document.body.appendChild(script); }, false); document.body.appendChild(script); } searching = false; // the guts of this userscript function main() { // Note, jQ replaces jQ to avoid conflicts. // Calendar // Find due date items calendarCreated = false; function myDateFunction(id, fromModal) { var date = $("#" + id).data("date"); var hasEvent = $("#" + id).data("hasEvent"); search.searchProjectTree('#due-'+date); calendarCreated = false; jQ('.cal_wrap').slideToggle(); return true; } jQ('#savedViewHUDButton').after(""); jQ('#showCalendar').click(function(e) { e.preventDefault(); if (!calendarCreated) { $('body').append('
') var dates = generateCalendarData(); $("#calendar").zabuto_calendar({ data: dates, action: function () { return myDateFunction(this.id, false); }, action_nav: function() { setTimeout(function() { $('.glyphicon-chevron-right').html('>'); $('.glyphicon-chevron-left').html('<'); },100); }, legend: [ {type: "text", label: "Tasks Incomplete"}, {type: "list", list: ["grade-1", "grade-2", "grade-3", "grade-4", "grade-5"]}, {type: "text", label: "Tasks Completed"} ], }); $('.glyphicon-chevron-right').html('>'); $('.glyphicon-chevron-left').html('<'); calendarCreated = true; } else { calendarCreated = false; jQ('.zabuto_calendar').remove(); } jQ('.cal_wrap').slideToggle(); }); // Insert recent links var recentLinks = ""; jQ('#savedViewHUDButton').after(""+recentLinks); jQ('#showRecentLinks').click(function(e) { e.preventDefault(); jQ('#recentLinksMenu').slideToggle(); }); jQ('#recentLink_1wk').click(function(e) { e.preventDefault(); if (jQ('#searchBox').val()=='last-changed:7d') { search.searchProjectTree(''); } else { search.searchProjectTree('last-changed:7d'); } }); jQ('#recentLink_24hrs').click(function(e) { e.preventDefault(); if (jQ('#searchBox').val()=='last-changed:1d') { search.searchProjectTree(''); } else { search.searchProjectTree('last-changed:1d'); } }); jQ('#recentLink_1hr').click(function(e) { e.preventDefault(); if (jQ('#searchBox').val()=='last-changed:1h') { search.searchProjectTree(''); } else { search.searchProjectTree('last-changed:1h'); } }); var generateTagList = function() { // Generate list of all hashtags var tags = $('.contentTagText'); var tagObjs = {}; tags.each(function(ii, obj) { var tag = jQ(obj).text().toLowerCase(); var tagObj = tagObjs[tag]; if (!tagObj) { tagObj = {'count':1}; } else { tagObj.count++; } tagObjs[tag] = tagObj; }); var tagObjsArray = []; for (var tag in tagObjs) { var tagObj = tagObjs[tag]; tagObj.tag = tag; tagObjsArray.push(tagObj); } return tagObjsArray.sort(function (a, b) { return b.count - a.count; }); }; var generateCalendarData = function() { var currentSearch = jQ('#searchBox').val(); // First let's delete the existing tags index, or else it will count those and old tags are never removed. search.searchProjectTree('#wf-tag-list'); $('.project.matches:last .notes .content').text(''); $('.project.matches:last .content').trigger('blur'); // Now find existing tags. showCompleted(); search.searchProjectTree('#due-'); var allDatedItems = generateTagList(); search.searchProjectTree('#due- is:complete'); var completedDateItems = generateTagList(); search.searchProjectTree(currentSearch); var dates = []; for (var ii in allDatedItems) { var dateItem = allDatedItems[ii]; console.log(dateItem); var tag = dateItem.tag; if (tag.substr(0,4)=='due-') { var count = dateItem.count; var completed = completedDateItems.filter(function (obj) { return obj.tag === tag; }); var completed_count = (completed[0]) ? completed[0].count : 0; var completed_pct = Math.round(100*(completed_count/count)); var className = 'grade-5'; switch (true) { case (completed_pct < 20): className = 'grade-1'; break; case (completed_pct > 20 && completed_pct < 40): className = 'grade-2'; break; case (completed_pct > 40 && completed_pct < 60): className = 'grade-3'; break; case (completed_pct > 60 && completed_pct < 80): className = 'grade-4'; break; case (completed_pct > 80): className = 'grade-5'; break; } dates.push({ "date":dateItem.tag.substr(4), "classname": className, "title": Math.round(100*(completed_count/count))+"% complete" }); } } console.log(dates); return dates; }; var generateTags = function() { var currentSearch = jQ('#searchBox').val(); // First let's delete the existing tags index, or else it will count those and old tags are never removed. search.searchProjectTree('#wf-tag-list'); $('.project.matches:last .notes .content').text(''); $('.project.matches:last .content').trigger('blur'); // Now find existing tags. search.searchProjectTree('#'); var allTags = generateTagList(); // Now find which of those are completed search.searchProjectTree('# is:complete'); var completedTags = generateTagList(); // Store the list of tags updateTagsNote(allTags); // Update the menu var tagLinkOutput = ''; for (var ii in allTags) { var count = allTags[ii]['count']; var tag = allTags[ii]['tag']; var completed = completedTags.filter(function (obj) { return obj.tag === tag; }); var completed_count = (completed[0]) ? completed[0].count : 0; tagLinkOutput += ""+count+" #"+tag+""; } $('#tagsMenu').html(tagLinkOutput); search.searchProjectTree(currentSearch); }; var generateTagsMenu = function () { // Ensure the search is ready. This will throw an exception if not. var currentSearch = jQ('#searchBox').val(); search.searchProjectTree('#wf-tag-list'); search.searchProjectTree(currentSearch); generateTags(); jQ('#savedViewHUDButton').after(""); jQ('#openTags').on('click',function(e) { e.preventDefault(); // If we're showing the tags menu, regenerate the tags list. Don't do it on hide. if ($('#tagsMenu:visible').length < 1) { generateTags(); } jQ('#tagsMenu').slideToggle(); }); }; var updateTagsNote = function(tagArray) { window.location.hash=''; search.searchProjectTree('#wf-tag-list'); var tagList = ''; for (var ii in tagArray) { var count = tagArray[ii]['count']; var tag = tagArray[ii]['tag']; tagList += count+" #"+tag+" - "; } $('.project.matches:last .notes .content').text('View Full List: '+tagList); $('.project.matches:last .content').trigger('blur'); }; var attemptTags = function() { setTimeout(function() { try { generateTagsMenu(); } catch(e) { attemptTags(); } },500); }; attemptTags(); // Add -rand functionality jQ(window).on('hashchange',function(e) { if (searching) { return false; } var query = jQ('#searchBox').val(); var needle=/(%23\w*-rand)+/; var match = window.location.href.match(needle); if (match) { // A tag with -rand on the end has been clicked. Locate another. searching = true; var tag = match[0]; //matches "2 chapters" tag = "#"+tag.slice(3); window.location.href='/#'; search.searchProjectTree(tag); var target = null; var count = 0; var tags = $('.contentMatch'); var random = $(tags[Math.floor(Math.random()*tags.length)])[0]; var parent = jQ(random).parents('.name').find('a').first(); var href = jQ(parent).attr('href'); window.location.href = href; setTimeout(function() { searching = false; },100); } }); // Add image popups var addImagePreviews = function() { jQ('a').each(function() { if (jQ(this).data('previewLoaded')) { return; } var url = this.href; var target = this; var img = null; function testImage(url, callback, timeout) { timeout = timeout || 5000; var timedOut = false, timer; img = new Image(); img.onerror = img.onabort = function() { if (!timedOut) { clearTimeout(timer); callback(url, 0); } }; img.onload = function() { if (!timedOut) { clearTimeout(timer); callback(url, 1); } }; img.src = url; timer = setTimeout(function() { timedOut = true; // reset .src to invalid URL so it stops previous // loading, but doesn't trigger new load img.src = "//!!!!/test.jpg"; callback(url, "timeout"); }, timeout); } testImage(url,function(url,loaded) { jQ(target).data('previewLoaded',true); if (loaded) { jQ(target).after(jQ(img).addClass('image-preview')); } },2000); }); }; // Add styles jQ('body').append(""); // ""+ // ""); // Add coloring styles (ala Paintly, stolen from: https://userstyles.org/styles/125832/re-workflowy-re-painter) setInterval(function() { $('.content').css('background-color',''); var colors = { 'red':'#FFB5B5', 'orange':'#FFD8B5', 'yellow':'#FFFAB5', 'lime':'#E1FFB5', 'olive':'#B5FFC9', 'green':'#CCFFB5', 'teal':'#B5FFD7', 'aquea':'#B5FFFC', 'blu':'#B5E8FF', 'navy':'#B9B5FF', 'fuchia':'#F1B5FF', 'purple':'#D3B5FF', 'maroon':'#C08F8F', 'silver':'silver', 'gray':'gray', 'black':'black', 'white':'white' }; for (var ii in colors) { var color = colors[ii]; $('.content:contains("#'+ii+'")').css('background-color',color); } addImagePreviews(); },500); } if(typeof jQuery=="undefined"){throw new Error("jQuery is not loaded")}$.fn.zabuto_calendar=function(options){var opts=$.extend({},$.fn.zabuto_calendar_defaults(),options);var languageSettings=$.fn.zabuto_calendar_language(opts.language);opts=$.extend({},opts,languageSettings);this.each(function(){var $calendarElement=$(this);$calendarElement.attr("id","zabuto_calendar_"+Math.floor(Math.random()*99999).toString(36));$calendarElement.data("initYear",opts.year);$calendarElement.data("initMonth",opts.month);$calendarElement.data("monthLabels",opts.month_labels);$calendarElement.data("weekStartsOn",opts.weekstartson);$calendarElement.data("navIcons",opts.nav_icon);$calendarElement.data("dowLabels",opts.dow_labels);$calendarElement.data("showToday",opts.today);$calendarElement.data("showDays",opts.show_days);$calendarElement.data("showPrevious",opts.show_previous);$calendarElement.data("showNext",opts.show_next);$calendarElement.data("cellBorder",opts.cell_border);$calendarElement.data("jsonData",opts.data);$calendarElement.data("ajaxSettings",opts.ajax);$calendarElement.data("legendList",opts.legend);$calendarElement.data("actionFunction",opts.action);$calendarElement.data("actionNavFunction",opts.action_nav);drawCalendar();function drawCalendar(){var dateInitYear=parseInt($calendarElement.data("initYear"));var dateInitMonth=parseInt($calendarElement.data("initMonth"))-1;var dateInitObj=new Date(dateInitYear,dateInitMonth,1,0,0,0,0);$calendarElement.data("initDate",dateInitObj);var tableClassHtml=$calendarElement.data("cellBorder")===true?" table-bordered":"";$tableObj=$('
');$tableObj=drawTable($calendarElement,$tableObj,dateInitObj.getFullYear(),dateInitObj.getMonth());$legendObj=drawLegend($calendarElement);var $containerHtml=$('
');$containerHtml.append($tableObj);$containerHtml.append($legendObj);$calendarElement.append($containerHtml);var jsonData=$calendarElement.data("jsonData");if(false!==jsonData){checkEvents($calendarElement,dateInitObj.getFullYear(),dateInitObj.getMonth())}}function drawTable($calendarElement,$tableObj,year,month){var dateCurrObj=new Date(year,month,1,0,0,0,0);$calendarElement.data("currDate",dateCurrObj);$tableObj.empty();$tableObj=appendMonthHeader($calendarElement,$tableObj,year,month);$tableObj=appendDayOfWeekHeader($calendarElement,$tableObj);$tableObj=appendDaysOfMonth($calendarElement,$tableObj,year,month);checkEvents($calendarElement,year,month);return $tableObj}function drawLegend($calendarElement){var $legendObj=$('
');var legend=$calendarElement.data("legendList");if(typeof legend=="object"&&legend.length>0){$(legend).each(function(index,item){if(typeof item=="object"){if("type"in item){var itemLabel="";if("label"in item){itemLabel=item.label}switch(item.type){case"text":if(itemLabel!==""){var itemBadge="";if("badge"in item){if(typeof item.classname==="undefined"){var badgeClassName="badge-event"}else{var badgeClassName=item.classname}itemBadge=''+item.badge+" "}$legendObj.append(''+itemBadge+itemLabel+"")}break;case"block":if(itemLabel!==""){itemLabel=""+itemLabel+""}if(typeof item.classname==="undefined"){var listClassName="event"}else{var listClassName="event-styled "+item.classname}$legendObj.append('