Event Merge for Google Calendar™ (by @imightbeAmy)

Script that visually merges the same event on multiple Google Calendars into one event.

当前为 2018-02-18 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Event Merge for Google Calendar™ (by @imightbeAmy)
  3. // @namespace gcal-multical-event-merge
  4. // @include https://www.google.com/calendar/*
  5. // @include http://www.google.com/calendar/*
  6. // @include https://calendar.google.com/*
  7. // @include http://calendar.google.com/*
  8. // @version 1
  9. // @grant none
  10. // @description Script that visually merges the same event on multiple Google Calendars into one event.
  11. // ==/UserScript==
  12.  
  13. 'use strict';
  14.  
  15. const stripesGradient = (colors, width, angle) => {
  16. let gradient = `repeating-linear-gradient( ${angle}deg,`;
  17. let pos = 0;
  18.  
  19. colors.forEach(color => {
  20. gradient += color + " " + pos + "px,";
  21. pos += width;
  22. gradient += color + " " + pos + "px,";
  23. });
  24. gradient = gradient.slice(0, -1);
  25. gradient += ")";
  26. return gradient;
  27. };
  28.  
  29. const dragType = e => parseInt(e.dataset.dragsourceType);
  30.  
  31. const merge = (mainCalender) => {
  32. const eventSets = {};
  33. const days = mainCalender.querySelectorAll("[role=\"gridcell\"]");
  34. days.forEach((day, index) => {
  35. const events = Array.from(day.querySelectorAll("[data-eventid][role=\"button\"], [data-eventid] [role=\"button\"]"));
  36. events.forEach(event => {
  37. const eventTitleEl = event.querySelector('[aria-hidden="true"]');
  38. if (!eventTitleEl) {
  39. return;
  40. }
  41. let eventKey = eventTitleEl.textContent.replace(/\\s+/g,"");
  42. eventKey = index + eventKey;
  43. eventSets[eventKey] = eventSets[eventKey] || [];
  44. eventSets[eventKey].push(event);
  45. });
  46. });
  47.  
  48. Object.values(eventSets)
  49. .forEach(events => {
  50. if (events.length > 1) {
  51. const colors = events.map(event =>
  52. event.style.backgroundColor || // Week day and full day events marked 'attending'
  53. event.style.borderColor || // Not attending or not responded week view events
  54. event.parentElement.style.borderColor // Timed month view events
  55. );
  56. events.sort((e1, e2) => dragType(e1) - dragType(e2));
  57.  
  58. const parentPosition = events[0].parentElement.getBoundingClientRect();
  59. const positions = events.map(event => {
  60. const eventPosition = event.getBoundingClientRect();
  61. event.originalLeft = event.originalLeft || eventPosition.left;
  62. event.originalRight = event.originalRight || eventPosition.right;
  63. return {
  64. left: eventPosition.left - parentPosition.left,
  65. right: parentPosition.right - eventPosition.right,
  66. }
  67. });
  68.  
  69. const eventToKeep = events.shift();
  70. events.forEach(event => {
  71. event.style.visibility = "hidden";
  72. });
  73.  
  74. if (eventToKeep.style.backgroundColor || eventToKeep.style.borderColor) {
  75. eventToKeep.style.backgroundImage = stripesGradient(colors, 10, 45);
  76. eventToKeep.style.left = Math.min.apply(Math, positions.map(s => s.left)) + 'px';
  77. eventToKeep.style.right = Math.min.apply(Math, positions.map(s => s.right)) + 'px';
  78. eventToKeep.style.visibility = "visible";
  79. eventToKeep.style.width = null;
  80. eventToKeep.style.border = "solid 1px #FFF"
  81.  
  82. events.forEach(event => {
  83. event.style.visibility = "hidden";
  84. });
  85. } else {
  86. const dots = eventToKeep.querySelector('[role="button"] div:first-child');
  87. const dot = dots.querySelector('div');
  88. dot.style.backgroundImage = stripesGradient(colors, 4, 90);
  89. dot.style.width = colors.length * 4 + 'px';
  90. dot.style.borderWidth = 0;
  91. dot.style.height = '8px';
  92.  
  93. events.forEach(event => {
  94. event.style.visibility = "hidden";
  95. });
  96. }
  97. } else {
  98. events.forEach(event => {
  99. event.style.visibility = "visible";
  100. });
  101. }
  102. });
  103. }
  104.  
  105. const init = (mutationsList) => {
  106. mutationsList && mutationsList
  107. .map(mutation => mutation.addedNodes[0] || mutation.target)
  108. .filter(node => node.matches && node.matches("[role=\"main\"], [role=\"dialog\"]"))
  109. .map(merge);
  110. }
  111.  
  112. setTimeout(() => chrome.storage.local.get('disabled', storage => {
  113. console.log(`Event merge is ${storage.disabled ? 'disabled' : 'enabled'}`);
  114. if (!storage.disabled) {
  115. const observer = new MutationObserver(init);
  116. observer.observe(document.querySelector('body'), { childList: true, subtree: true, attributes: true });
  117. }
  118.  
  119. chrome.storage.onChanged.addListener(() => window.location.reload())
  120. }), 10);