Workflowier

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

  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.2
  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.  
  31. // Calendar
  32. // Find due date items
  33. calendarCreated = false;
  34.  
  35.  
  36. function myDateFunction(id, fromModal) {
  37. var date = $("#" + id).data("date");
  38. var hasEvent = $("#" + id).data("hasEvent");
  39. search.searchProjectTree('#due-'+date);
  40. calendarCreated = false;
  41. jQ('.cal_wrap').slideToggle();
  42. return true;
  43. }
  44.  
  45. jQ('#savedViewHUDButton').after("<div class='menuButton button'><div class='topBarButtonTextContainer'><a href='#' id='showCalendar'>Calendar</a></div></div>");
  46. jQ('#showCalendar').click(function(e) {
  47. e.preventDefault();
  48. if (!calendarCreated) {
  49. $('body').append('<div id="calendar" class="cal_wrap"></div>')
  50. var dates = generateCalendarData();
  51. $("#calendar").zabuto_calendar({
  52. data: dates,
  53. action: function () {
  54. return myDateFunction(this.id, false);
  55. },
  56. action_nav: function() {
  57. setTimeout(function() {
  58. $('.glyphicon-chevron-right').html('&gt;');
  59. $('.glyphicon-chevron-left').html('&lt;');
  60. },100);
  61. },
  62. legend: [
  63. {type: "text", label: "Tasks Incomplete"},
  64. {type: "list", list: ["grade-1", "grade-2", "grade-3", "grade-4", "grade-5"]},
  65. {type: "text", label: "Tasks Completed"}
  66. ],
  67. });
  68.  
  69. $('.glyphicon-chevron-right').html('&gt;');
  70. $('.glyphicon-chevron-left').html('&lt;');
  71. calendarCreated = true;
  72. } else {
  73. calendarCreated = false;
  74. jQ('.zabuto_calendar').remove();
  75. }
  76. jQ('.cal_wrap').slideToggle();
  77. });
  78.  
  79. // Insert recent links
  80. var recentLinks = "<div class='menu-options' id='recentLinksMenu'>"+
  81. "<div class='button'><div class='topBarButtonTextContainer'><a href='#' id='recentLink_1wk'>This Week</a></div></div>"+
  82. "<div class='button'><div class='topBarButtonTextContainer'><a href='#' class='button' id='recentLink_24hrs'>Today</a></div></div>"+
  83. "<div class='button'><div class='topBarButtonTextContainer'><a href='#' class='button' id='recentLink_1hr'>Last Hour</a></div></div>"+
  84. "</div>";
  85. jQ('#savedViewHUDButton').after("<div class='menuButton button'><div class='topBarButtonTextContainer'><a href='#' id='showRecentLinks'>Recent</a></div></div>"+recentLinks);
  86.  
  87. jQ('#showRecentLinks').click(function(e) {
  88. e.preventDefault();
  89. jQ('#recentLinksMenu').slideToggle();
  90. });
  91.  
  92. jQ('#recentLink_1wk').click(function(e) {
  93. e.preventDefault();
  94. if (jQ('#searchBox').val()=='last-changed:7d') {
  95. search.searchProjectTree('');
  96. } else {
  97. search.searchProjectTree('last-changed:7d');
  98. }
  99. });
  100. jQ('#recentLink_24hrs').click(function(e) {
  101. e.preventDefault();
  102. if (jQ('#searchBox').val()=='last-changed:1d') {
  103. search.searchProjectTree('');
  104. } else {
  105. search.searchProjectTree('last-changed:1d');
  106. }
  107. });
  108. jQ('#recentLink_1hr').click(function(e) {
  109. e.preventDefault();
  110. if (jQ('#searchBox').val()=='last-changed:1h') {
  111. search.searchProjectTree('');
  112. } else {
  113. search.searchProjectTree('last-changed:1h');
  114. }
  115. });
  116.  
  117. var generateTagList = function() {
  118. // Generate list of all hashtags
  119. var tags = $('.contentTagText');
  120. var tagObjs = {};
  121. tags.each(function(ii, obj) {
  122. var tag = jQ(obj).text().toLowerCase();
  123.  
  124. var tagObj = tagObjs[tag];
  125. if (!tagObj) {
  126. tagObj = {'count':1};
  127. } else {
  128. tagObj.count++;
  129. }
  130. tagObjs[tag] = tagObj;
  131. });
  132. var tagObjsArray = [];
  133. for (var tag in tagObjs) {
  134. var tagObj = tagObjs[tag];
  135. tagObj.tag = tag;
  136. tagObjsArray.push(tagObj);
  137. }
  138. return tagObjsArray.sort(function (a, b) {
  139. return b.count - a.count;
  140. });
  141. };
  142.  
  143. var generateCalendarData = function() {
  144. var currentSearch = jQ('#searchBox').val();
  145. // First let's delete the existing tags index, or else it will count those and old tags are never removed.
  146. search.searchProjectTree('#wf-tag-list');
  147. $('.project.matches:last .notes .content').text('');
  148. $('.project.matches:last .content').trigger('blur');
  149. // Now find existing tags.
  150. showCompleted();
  151. search.searchProjectTree('#due-');
  152. var allDatedItems = generateTagList();
  153. search.searchProjectTree('#due- is:complete');
  154. var completedDateItems = generateTagList();
  155.  
  156. search.searchProjectTree(currentSearch);
  157.  
  158. var dates = [];
  159. for (var ii in allDatedItems) {
  160. var dateItem = allDatedItems[ii];
  161. console.log(dateItem);
  162. var tag = dateItem.tag;
  163. if (tag.substr(0,4)=='due-') {
  164. var count = dateItem.count;
  165. var completed = completedDateItems.filter(function (obj) {
  166. return obj.tag === tag;
  167. });
  168. var completed_count = (completed[0]) ? completed[0].count : 0;
  169. var completed_pct = Math.round(100*(completed_count/count));
  170. var className = 'grade-5';
  171. switch (true) {
  172. case (completed_pct < 20):
  173. className = 'grade-1';
  174. break;
  175. case (completed_pct > 20 && completed_pct < 40):
  176. className = 'grade-2';
  177. break;
  178. case (completed_pct > 40 && completed_pct < 60):
  179. className = 'grade-3';
  180. break;
  181. case (completed_pct > 60 && completed_pct < 80):
  182. className = 'grade-4';
  183. break;
  184. case (completed_pct > 80):
  185. className = 'grade-5';
  186. break;
  187. }
  188.  
  189. dates.push({
  190. "date":dateItem.tag.substr(4),
  191. "classname": className,
  192. "title": Math.round(100*(completed_count/count))+"% complete"
  193. });
  194. }
  195. }
  196. console.log(dates);
  197. return dates;
  198. };
  199.  
  200. var generateTags = function() {
  201. var currentSearch = jQ('#searchBox').val();
  202. // First let's delete the existing tags index, or else it will count those and old tags are never removed.
  203. search.searchProjectTree('#wf-tag-list');
  204. $('.project.matches:last .notes .content').text('');
  205. $('.project.matches:last .content').trigger('blur');
  206. // Now find existing tags.
  207. search.searchProjectTree('#');
  208. var allTags = generateTagList();
  209. // Now find which of those are completed
  210. search.searchProjectTree('# is:complete');
  211. var completedTags = generateTagList();
  212.  
  213. // Store the list of tags
  214. updateTagsNote(allTags);
  215.  
  216. // Update the menu
  217. var tagLinkOutput = '';
  218. for (var ii in allTags) {
  219. var count = allTags[ii]['count'];
  220. var tag = allTags[ii]['tag'];
  221. var completed = completedTags.filter(function (obj) {
  222. return obj.tag === tag;
  223. });
  224. var completed_count = (completed[0]) ? completed[0].count : 0;
  225. tagLinkOutput += "<a href='/#/"+tag+"?q=%23"+tag+"' title='"+Math.round(100*(completed_count/count))+"% "+completed_count+"/"+count+" complete.'><strong>"+count+"</strong> #"+tag+"</a>";
  226. }
  227. $('#tagsMenu').html(tagLinkOutput);
  228. search.searchProjectTree(currentSearch);
  229.  
  230. };
  231.  
  232. var generateTagsMenu = function () {
  233. // Ensure the search is ready. This will throw an exception if not.
  234. var currentSearch = jQ('#searchBox').val();
  235. search.searchProjectTree('#wf-tag-list');
  236. search.searchProjectTree(currentSearch);
  237.  
  238. generateTags();
  239. 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>");
  240. jQ('#openTags').on('click',function(e) {
  241. e.preventDefault();
  242. // If we're showing the tags menu, regenerate the tags list. Don't do it on hide.
  243. if ($('#tagsMenu:visible').length < 1) {
  244. generateTags();
  245. }
  246. jQ('#tagsMenu').slideToggle();
  247. });
  248. };
  249.  
  250. var updateTagsNote = function(tagArray) {
  251. window.location.hash='';
  252. search.searchProjectTree('#wf-tag-list');
  253. var tagList = '';
  254. for (var ii in tagArray) {
  255. var count = tagArray[ii]['count'];
  256. var tag = tagArray[ii]['tag'];
  257. tagList += count+" #"+tag+" - ";
  258. }
  259.  
  260. $('.project.matches:last .notes .content').text('View Full List: '+tagList);
  261. $('.project.matches:last .content').trigger('blur');
  262. };
  263.  
  264. var attemptTags = function() {
  265. setTimeout(function() {
  266. try {
  267. generateTagsMenu();
  268. } catch(e) {
  269. attemptTags();
  270. }
  271. },500);
  272. };
  273.  
  274. attemptTags();
  275.  
  276. // Add -rand functionality
  277. jQ(window).on('hashchange',function(e) {
  278. if (searching) {
  279. return false;
  280. }
  281.  
  282. var query = jQ('#searchBox').val();
  283. var needle=/(%23\w*-rand)+/;
  284. var match = window.location.href.match(needle);
  285. if (match) {
  286. // A tag with -rand on the end has been clicked. Locate another.
  287. searching = true;
  288. var tag = match[0]; //matches "2 chapters"
  289. tag = "#"+tag.slice(3);
  290. window.location.href='/#';
  291. search.searchProjectTree(tag);
  292. var target = null;
  293. var count = 0;
  294. var tags = $('.contentMatch');
  295. var random = $(tags[Math.floor(Math.random()*tags.length)])[0];
  296. var parent = jQ(random).parents('.name').find('a').first();
  297. var href = jQ(parent).attr('href');
  298. window.location.href = href;
  299. setTimeout(function() {
  300. searching = false;
  301. },100);
  302. }
  303. });
  304.  
  305. // Add image popups
  306. var addImagePreviews = function() {
  307. jQ('a').each(function() {
  308. if (jQ(this).data('previewLoaded')) {
  309. return;
  310. }
  311. var url = this.href;
  312. var target = this;
  313. var img = null;
  314. function testImage(url, callback, timeout) {
  315. timeout = timeout || 5000;
  316. var timedOut = false, timer;
  317. img = new Image();
  318. img.onerror = img.onabort = function() {
  319. if (!timedOut) {
  320. clearTimeout(timer);
  321. callback(url, 0);
  322. }
  323. };
  324. img.onload = function() {
  325. if (!timedOut) {
  326. clearTimeout(timer);
  327. callback(url, 1);
  328. }
  329. };
  330. img.src = url;
  331. timer = setTimeout(function() {
  332. timedOut = true;
  333. // reset .src to invalid URL so it stops previous
  334. // loading, but doesn't trigger new load
  335. img.src = "//!!!!/test.jpg";
  336. callback(url, "timeout");
  337. }, timeout);
  338. }
  339. testImage(url,function(url,loaded) {
  340. jQ(target).data('previewLoaded',true);
  341. if (loaded) {
  342. jQ(target).after(jQ(img).addClass('image-preview'));
  343. }
  344. },2000);
  345. });
  346. };
  347.  
  348. // Add styles
  349. jQ('body').append("<style>"+
  350. ".image-preview { height: 100px; display: block; } "+
  351. ".image-preview:hover { height: initial; display: block; } "+
  352. "#tagsMenu{ height:300px; overflow:scroll; max-width: 250px; right: 140px; }"+
  353. "#tagsMenu a { margin: 0 5px; display: block; }"+
  354. "#recentLinksMenu{ right:400px; }"+
  355. ".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;}"+
  356. "div.zabuto_calendar{margin:0;padding:0}div.zabuto_calendar .table{width:100%;margin:0;padding:0}div.zabuto_calendar .table th,div.zabuto_calendar .table td{padding:4px 2px;text-align:center}div.zabuto_calendar .table tr th,div.zabuto_calendar .table tr td{background-color:#fff}div.zabuto_calendar .table tr.calendar-month-header th{background-color:#fafafa}div.zabuto_calendar .table tr.calendar-month-header th span{cursor:pointer;display:inline-block;padding-bottom:10px}div.zabuto_calendar .table tr.calendar-dow-header th{background-color:#f0f0f0}div.zabuto_calendar .table tr:last-child{border-bottom:1px solid #ddd}div.zabuto_calendar .table tr.calendar-month-header th{padding-top:12px;padding-bottom:4px}div.zabuto_calendar .table-bordered tr.calendar-month-header th{border-left:0;border-right:0}div.zabuto_calendar .table-bordered tr.calendar-month-header th:first-child{border-left:1px solid #ddd}div.zabuto_calendar div.calendar-month-navigation{cursor:pointer;margin:0;padding:0;padding-top:5px}div.zabuto_calendar tr.calendar-dow-header th,div.zabuto_calendar tr.calendar-dow td{width:14%}div.zabuto_calendar .table tr td div.day{margin:0;padding-top:7px;padding-bottom:7px}div.zabuto_calendar .table tr td.event div.day,div.zabuto_calendar ul.legend li.event{background-color:#fff0c3}div.zabuto_calendar .table tr td.dow-clickable,div.zabuto_calendar .table tr td.event-clickable{cursor:pointer}div.zabuto_calendar .badge-today,div.zabuto_calendar div.legend span.badge-today{background-color:#357ebd;color:#fff;text-shadow:none}div.zabuto_calendar .badge-event,div.zabuto_calendar div.legend span.badge-event{background-color:#ff9b08;color:#fff;text-shadow:none}div.zabuto_calendar .badge-event{font-size:.95em;padding-left:8px;padding-right:8px;padding-bottom:4px}div.zabuto_calendar div.legend{margin-top:5px;text-align:right}div.zabuto_calendar div.legend span{color:#999;font-size:10px;font-weight:normal}div.zabuto_calendar div.legend span.legend-text:after,div.zabuto_calendar div.legend span.legend-block:after,div.zabuto_calendar div.legend span.legend-list:after,div.zabuto_calendar div.legend span.legend-spacer:after{content:' '}div.zabuto_calendar div.legend span.legend-spacer{padding-left:25px}div.zabuto_calendar ul.legend>span{padding-left:2px}div.zabuto_calendar ul.legend{display:inline-block;list-style:none outside none;margin:0;padding:0}div.zabuto_calendar ul.legend li{display:inline-block;height:11px;width:11px;margin-left:5px}div.zabuto_calendar ul.legend div.zabuto_calendar ul.legend li:first-child{margin-left:7px}div.zabuto_calendar ul.legend li:last-child{margin-right:5px}div.zabuto_calendar div.legend span.badge{font-size:.9em;border-radius:5px 5px 5px 5px;padding-left:5px;padding-right:5px;padding-top:2px;padding-bottom:3px}@media(max-width:979px){div.zabuto_calendar .table th,div.zabuto_calendar .table td{padding:2px 1px}}"+
  357. ".cal_wrap { position: absolute; top: 2em; z-index: 2000; display: none; background-color: white;padding: 1em;border: solid gray 3px;border-radius: 5px;} "+
  358. ".grade-1 {background-color: #FA2601;} .grade-2 {background-color: #FA8A00;} .grade-3 {background-color: #FFEB00;} .grade-4 {background-color: #27AB00;} .grade-5 {background-color: #27AB00;} "+
  359. "</style>");
  360. // "<link rel='stylesheet' href='//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css'>"+
  361. // "<script src='//netdna.bootstrapcdn.com/bootstrap/3.0.2/js/bootstrap.min.js'></script>");
  362.  
  363. // Add coloring styles (ala Paintly, stolen from: https://userstyles.org/styles/125832/re-workflowy-re-painter)
  364. setInterval(function() {
  365. $('.content').css('background-color','');
  366. var colors = {
  367. 'red':'#FFB5B5',
  368. 'orange':'#FFD8B5',
  369. 'yellow':'#FFFAB5',
  370. 'lime':'#E1FFB5',
  371. 'olive':'#B5FFC9',
  372. 'green':'#CCFFB5',
  373. 'teal':'#B5FFD7',
  374. 'aquea':'#B5FFFC',
  375. 'blu':'#B5E8FF',
  376. 'navy':'#B9B5FF',
  377. 'fuchia':'#F1B5FF',
  378. 'purple':'#D3B5FF',
  379. 'maroon':'#C08F8F',
  380. 'silver':'silver',
  381. 'gray':'gray',
  382. 'black':'black',
  383. 'white':'white'
  384. };
  385. for (var ii in colors) {
  386. var color = colors[ii];
  387. $('.content:contains("#'+ii+'")').css('background-color',color);
  388. }
  389. addImagePreviews();
  390. },500);
  391. }
  392.  
  393. 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=$('<table class="table'+tableClassHtml+'"></table>');$tableObj=drawTable($calendarElement,$tableObj,dateInitObj.getFullYear(),dateInitObj.getMonth());$legendObj=drawLegend($calendarElement);var $containerHtml=$('<div class="zabuto_calendar" id="'+$calendarElement.attr("id")+'"></div>');$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=$('<div class="legend" id="'+$calendarElement.attr("id")+'_legend"></div>');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='<span class="badge '+badgeClassName+'">'+item.badge+"</span> "}$legendObj.append('<span class="legend-'+item.type+'">'+itemBadge+itemLabel+"</span>")}break;case"block":if(itemLabel!==""){itemLabel="<span>"+itemLabel+"</span>"}if(typeof item.classname==="undefined"){var listClassName="event"}else{var listClassName="event-styled "+item.classname}$legendObj.append('<span class="legend-'+item.type+'"><ul class="legend"><li class="'+listClassName+'"></li></u>'+itemLabel+"</span>");break;case"list":if("list"in item&&typeof item.list=="object"&&item.list.length>0){var $legendUl=$('<ul class="legend"></u>');$(item.list).each(function(listIndex,listClassName){$legendUl.append('<li class="'+listClassName+'"></li>')});$legendObj.append($legendUl)}break;case"spacer":$legendObj.append('<span class="legend-'+item.type+'"> </span>');break}}}})}return $legendObj}function appendMonthHeader($calendarElement,$tableObj,year,month){var navIcons=$calendarElement.data("navIcons");var $prevMonthNavIcon=$('<span><span class="glyphicon glyphicon-chevron-left"></span></span>');var $nextMonthNavIcon=$('<span><span class="glyphicon glyphicon-chevron-right"></span></span>');if(typeof navIcons==="object"){if("prev"in navIcons){$prevMonthNavIcon.html(navIcons.prev)}if("next"in navIcons){$nextMonthNavIcon.html(navIcons.next)}}var prevIsValid=$calendarElement.data("showPrevious");if(typeof prevIsValid==="number"||prevIsValid===false){prevIsValid=checkMonthLimit($calendarElement.data("showPrevious"),true)}var $prevMonthNav=$('<div class="calendar-month-navigation"></div>');$prevMonthNav.attr("id",$calendarElement.attr("id")+"_nav-prev");$prevMonthNav.data("navigation","prev");if(prevIsValid!==false){prevMonth=month-1;prevYear=year;if(prevMonth==-1){prevYear=prevYear-1;prevMonth=11}$prevMonthNav.data("to",{year:prevYear,month:prevMonth+1});$prevMonthNav.append($prevMonthNavIcon);if(typeof $calendarElement.data("actionNavFunction")==="function"){$prevMonthNav.click($calendarElement.data("actionNavFunction"))}$prevMonthNav.click(function(e){drawTable($calendarElement,$tableObj,prevYear,prevMonth)})}var nextIsValid=$calendarElement.data("showNext");if(typeof nextIsValid==="number"||nextIsValid===false){nextIsValid=checkMonthLimit($calendarElement.data("showNext"),false)}var $nextMonthNav=$('<div class="calendar-month-navigation"></div>');$nextMonthNav.attr("id",$calendarElement.attr("id")+"_nav-next");$nextMonthNav.data("navigation","next");if(nextIsValid!==false){nextMonth=month+1;nextYear=year;if(nextMonth==12){nextYear=nextYear+1;nextMonth=0}$nextMonthNav.data("to",{year:nextYear,month:nextMonth+1});$nextMonthNav.append($nextMonthNavIcon);if(typeof $calendarElement.data("actionNavFunction")==="function"){$nextMonthNav.click($calendarElement.data("actionNavFunction"))}$nextMonthNav.click(function(e){drawTable($calendarElement,$tableObj,nextYear,nextMonth)})}var monthLabels=$calendarElement.data("monthLabels");var $prevMonthCell=$("<th></th>").append($prevMonthNav);var $nextMonthCell=$("<th></th>").append($nextMonthNav);var $currMonthLabel=$("<span>"+monthLabels[month]+" "+year+"</span>");$currMonthLabel.dblclick(function(){var dateInitObj=$calendarElement.data("initDate");drawTable($calendarElement,$tableObj,dateInitObj.getFullYear(),dateInitObj.getMonth())});var $currMonthCell=$('<th colspan="5"></th>');$currMonthCell.append($currMonthLabel);var $monthHeaderRow=$('<tr class="calendar-month-header"></tr>');$monthHeaderRow.append($prevMonthCell,$currMonthCell,$nextMonthCell);$tableObj.append($monthHeaderRow);return $tableObj}function appendDayOfWeekHeader($calendarElement,$tableObj){if($calendarElement.data("showDays")===true){var weekStartsOn=$calendarElement.data("weekStartsOn");var dowLabels=$calendarElement.data("dowLabels");if(weekStartsOn===0){var dowFull=$.extend([],dowLabels);var sunArray=new Array(dowFull.pop());dowLabels=sunArray.concat(dowFull)}var $dowHeaderRow=$('<tr class="calendar-dow-header"></tr>');$(dowLabels).each(function(index,value){$dowHeaderRow.append("<th>"+value+"</th>")});$tableObj.append($dowHeaderRow)}return $tableObj}function appendDaysOfMonth($calendarElement,$tableObj,year,month){var ajaxSettings=$calendarElement.data("ajaxSettings");var weeksInMonth=calcWeeksInMonth(year,month);var lastDayinMonth=calcLastDayInMonth(year,month);var firstDow=calcDayOfWeek(year,month,1);var lastDow=calcDayOfWeek(year,month,lastDayinMonth);var currDayOfMonth=1;var weekStartsOn=$calendarElement.data("weekStartsOn");if(weekStartsOn===0){if(lastDow==6){weeksInMonth++}if(firstDow==6&&(lastDow==0||lastDow==1||lastDow==5)){weeksInMonth--}firstDow++;if(firstDow==7){firstDow=0}}for(var wk=0;wk<weeksInMonth;wk++){var $dowRow=$('<tr class="calendar-dow"></tr>');for(var dow=0;dow<7;dow++){if(dow<firstDow||currDayOfMonth>lastDayinMonth){$dowRow.append("<td></td>")}else{var dateId=$calendarElement.attr("id")+"_"+dateAsString(year,month,currDayOfMonth);var dayId=dateId+"_day";var $dayElement=$('<div id="'+dayId+'" class="day" >'+currDayOfMonth+"</div>");$dayElement.data("day",currDayOfMonth);if($calendarElement.data("showToday")===true){if(isToday(year,month,currDayOfMonth)){$dayElement.html('<span class="badge badge-today">'+currDayOfMonth+"</span>")}}var $dowElement=$('<td id="'+dateId+'"></td>');$dowElement.append($dayElement);$dowElement.data("date",dateAsString(year,month,currDayOfMonth));$dowElement.data("hasEvent",false);if(typeof $calendarElement.data("actionFunction")==="function"){$dowElement.addClass("dow-clickable");$dowElement.click(function(){$calendarElement.data("selectedDate",$(this).data("date"))});$dowElement.click($calendarElement.data("actionFunction"))}$dowRow.append($dowElement);currDayOfMonth++}if(dow==6){firstDow=0}}$tableObj.append($dowRow)}return $tableObj}function createModal(id,title,body,footer){var $modalHeaderButton=$('<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>');var $modalHeaderTitle=$('<h4 class="modal-title" id="'+id+'_modal_title">'+title+"</h4>");var $modalHeader=$('<div class="modal-header"></div>');$modalHeader.append($modalHeaderButton);$modalHeader.append($modalHeaderTitle);var $modalBody=$('<div class="modal-body" id="'+id+'_modal_body">'+body+"</div>");var $modalFooter=$('<div class="modal-footer" id="'+id+'_modal_footer"></div>');if(typeof footer!=="undefined"){var $modalFooterAddOn=$("<div>"+footer+"</div>");$modalFooter.append($modalFooterAddOn)}var $modalContent=$('<div class="modal-content"></div>');$modalContent.append($modalHeader);$modalContent.append($modalBody);$modalContent.append($modalFooter);var $modalDialog=$('<div class="modal-dialog"></div>');$modalDialog.append($modalContent);var $modalFade=$('<div class="modal fade" id="'+id+'_modal" tabindex="-1" role="dialog" aria-labelledby="'+id+'_modal_title" aria-hidden="true"></div>');$modalFade.append($modalDialog);$modalFade.data("dateId",id);$modalFade.attr("dateId",id);return $modalFade}function checkEvents($calendarElement,year,month){var jsonData=$calendarElement.data("jsonData");var ajaxSettings=$calendarElement.data("ajaxSettings");$calendarElement.data("events",false);if(false!==jsonData){return jsonEvents($calendarElement)}else if(false!==ajaxSettings){return ajaxEvents($calendarElement,year,month)}return true}function jsonEvents($calendarElement){var jsonData=$calendarElement.data("jsonData");$calendarElement.data("events",jsonData);drawEvents($calendarElement,"json");return true}function ajaxEvents($calendarElement,year,month){var ajaxSettings=$calendarElement.data("ajaxSettings");if(typeof ajaxSettings!="object"||typeof ajaxSettings.url=="undefined"){alert("Invalid calendar event settings");return false}var data={year:year,month:month+1};$.ajax({type:"GET",url:ajaxSettings.url,data:data,dataType:"json"}).done(function(response){var events=[];$.each(response,function(k,v){events.push(response[k])});$calendarElement.data("events",events);drawEvents($calendarElement,"ajax")});return true}function drawEvents($calendarElement,type){var jsonData=$calendarElement.data("jsonData");var ajaxSettings=$calendarElement.data("ajaxSettings");var events=$calendarElement.data("events");if(events!==false){$(events).each(function(index,value){var id=$calendarElement.attr("id")+"_"+value.date;var $dowElement=$("#"+id);var $dayElement=$("#"+id+"_day");$dowElement.data("hasEvent",true);if(typeof value.title!=="undefined"){$dowElement.attr("title",value.title)}if(typeof value.classname==="undefined"){$dowElement.addClass("event")}else{$dowElement.addClass("event-styled");$dayElement.addClass(value.classname)}if(typeof value.badge!=="undefined"&&value.badge!==false){var badgeClass=value.badge===true?"":" badge-"+value.badge;var dayLabel=$dayElement.data("day");$dayElement.html('<span class="badge badge-event'+badgeClass+'">'+dayLabel+"</span>")}if(typeof value.body!=="undefined"){var modalUse=false;if(type==="json"&&typeof value.modal!=="undefined"&&value.modal===true){modalUse=true}else if(type==="ajax"&&"modal"in ajaxSettings&&ajaxSettings.modal===true){modalUse=true}if(modalUse===true){$dowElement.addClass("event-clickable");var $modalElement=createModal(id,value.title,value.body,value.footer);$("body").append($modalElement);$("#"+id).click(function(){$("#"+id+"_modal").modal()})}}})}}function isToday(year,month,day){var todayObj=new Date;var dateObj=new Date(year,month,day);return dateObj.toDateString()==todayObj.toDateString()}function dateAsString(year,month,day){d=day<10?"0"+day:day;m=month+1;m=m<10?"0"+m:m;return year+"-"+m+"-"+d}function calcDayOfWeek(year,month,day){var dateObj=new Date(year,month,day,0,0,0,0);var dow=dateObj.getDay();if(dow==0){dow=6}else{dow--}return dow}function calcLastDayInMonth(year,month){var day=28;while(checkValidDate(year,month+1,day+1)){day++}return day}function calcWeeksInMonth(year,month){var daysInMonth=calcLastDayInMonth(year,month);var firstDow=calcDayOfWeek(year,month,1);var lastDow=calcDayOfWeek(year,month,daysInMonth);var days=daysInMonth;var correct=firstDow-lastDow;if(correct>0){days+=correct}return Math.ceil(days/7)}function checkValidDate(y,m,d){return m>0&&m<13&&y>0&&y<32768&&d>0&&d<=new Date(y,m,0).getDate()}function checkMonthLimit(count,invert){if(count===false){count=0}var d1=$calendarElement.data("currDate");var d2=$calendarElement.data("initDate");var months;months=(d2.getFullYear()-d1.getFullYear())*12;months-=d1.getMonth()+1;months+=d2.getMonth();if(invert===true){if(months<parseInt(count)-1){return true}}else{if(months>=0-parseInt(count)){return true}}return false}});return this};$.fn.zabuto_calendar_defaults=function(){var now=new Date;var year=now.getFullYear();var month=now.getMonth()+1;var settings={language:false,year:year,month:month,show_previous:true,show_next:true,cell_border:false,today:false,show_days:true,weekstartson:1,nav_icon:false,data:false,ajax:false,legend:false,action:false,action_nav:false};return settings};$.fn.zabuto_calendar_language=function(lang){if(typeof lang=="undefined"||lang===false){lang="en"}switch(lang.toLowerCase()){case"de":return{month_labels:["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"],dow_labels:["Mo","Di","Mi","Do","Fr","Sa","So"]};break;case"en":return{month_labels:["January","February","March","April","May","June","July","August","September","October","November","December"],dow_labels:["Mon","Tue","Wed","Thu","Fri","Sat","Sun"]};break;case"ar":return{month_labels:["يناير","فبراير","مارس","أبريل","مايو","يونيو","يوليو","أغسطس","سبتمبر","أكتوبر","نوفمبر","ديسمبر"],dow_labels:["أثنين","ثلاثاء","اربعاء","خميس","جمعه","سبت","أحد"]};break;case"es":return{month_labels:["Enero","Febrero","Marzo","Abril","Mayo","Junio","Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre"],dow_labels:["Lu","Ma","Mi","Ju","Vi","Sá","Do"]};break;case"fr":return{month_labels:["Janvier","Février","Mars","Avril","Mai","Juin","Juillet","Août","Septembre","Octobre","Novembre","Décembre"],dow_labels:["Lun","Mar","Mer","Jeu","Ven","Sam","Dim"]};break;case"it":return{month_labels:["Gennaio","Febbraio","Marzo","Aprile","Maggio","Giugno","Luglio","Agosto","Settembre","Ottobre","Novembre","Dicembre"],dow_labels:["Lun","Mar","Mer","Gio","Ven","Sab","Dom"]};break;case"nl":return{month_labels:["Januari","Februari","Maart","April","Mei","Juni","Juli","Augustus","September","Oktober","November","December"],dow_labels:["Ma","Di","Wo","Do","Vr","Za","Zo"]};break;case"pl":return{month_labels:["Styczeń","Luty","Marzec","Kwiecień","Maj","Czerwiec","Lipiec","Sierpień","Wrzesień","Październik","Listopad","Grudzień"],dow_labels:["pon.","wt.","śr.","czw.","pt.","sob.","niedz."]};break;case"pt":return{month_labels:["Janeiro","Fevereiro","Marco","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"],dow_labels:["S","T","Q","Q","S","S","D"]};break;case"ru":return{month_labels:["Январь","Февраль","Март","Апрель","Май","Июнь","Июль","Август","Сентябрь","Октябрь","Ноябрь","Декабрь"],dow_labels:["Пн","Вт","Ср","Чт","Пт","Сб","Вск"]};break;case"se":return{month_labels:["Januari","Februari","Mars","April","Maj","Juni","Juli","Augusti","September","Oktober","November","December"],dow_labels:["Mån","Tis","Ons","Tor","Fre","Lör","Sön"]};break;case"tr":return{month_labels:["Ocak","Şubat","Mart","Nisan","Mayıs","Haziran","Temmuz","Ağustos","Eylül","Ekim","Kasım","Aralık"],dow_labels:["Pts","Salı","Çar","Per","Cuma","Cts","Paz"]};break}};
  394.  
  395. // load jQuery and execute the main function
  396. addJQuery(main);