您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Merge same events on from calendars in Google Calendar. Fork of imightbeamy/gcal-multical-event-merge cross-browser compatible.
当前为
// ==UserScript== // @name Merge events for Google Calendar // @namespace https://gf.qytechs.cn/en/users/6254-doublemms // @license MIT // @description Merge same events on from calendars in Google Calendar. Fork of imightbeamy/gcal-multical-event-merge cross-browser compatible. // @include https://www.google.com/calendar/* // @include http://www.google.com/calendar/* // @include https://calendar.google.com/* // @include http://calendar.google.com/* // @version 1 // @grant none // ==/UserScript== (function () { "use strict"; /** * Return gradient style from multiple color inputs * @param {string[]} colors List of colors (eg. ['#fff', '#000']) * @param {number} width Grandient bars width in pixels * @param {number} angle Orientation angle for bars * @return {string} CSS style */ const generateGradient = (colors, width, angle) => { let gradient = `repeating-linear-gradient( ${angle}deg,`; let pos = 0; colors.forEach((color) => { gradient += color + " " + pos + "px,"; pos += width; gradient += color + " " + pos + "px,"; }); gradient = gradient.slice(0, -1); gradient += ")"; return gradient; }; /** * Calculate event position * @param {HTMLElement} event * @param {DOMRect} parentPosition * @return {DOMRect} */ const calculateEventPosition = (event, parentPosition) => { const eventPosition = event.getBoundingClientRect(); return { left: Math.max(eventPosition.left - parentPosition.left, 0), right: parentPosition.right - eventPosition.right, }; }; /** * Merge multiple events elements * @param {HTMLElement[]} events * @returns {void} */ const mergeEventElements = (events) => { /** * Parse event drag source type * @param {HTMLElement} e * @returns {number} */ const dragType = (e) => parseInt(e.dataset.dragsourceType); events.sort((e1, e2) => dragType(e1) - dragType(e2)); const colors = events.map( (event) => event.style.backgroundColor || // Week day and full day events marked 'attending' event.style.borderColor || // Not attending or not responded week view events event.parentElement.style.borderColor // Timed month view events ); const parentPosition = events[0].parentElement.getBoundingClientRect(); const positions = events.map((event) => { event.originalPosition = event.originalPosition || calculateEventPosition(event, parentPosition); return event.originalPosition; }); const eventToKeep = events.shift(); events.forEach((event) => { event.style.visibility = "hidden"; }); if (eventToKeep.style.backgroundColor || eventToKeep.style.borderColor) { eventToKeep.originalStyle = eventToKeep.originalStyle || { backgroundImage: eventToKeep.style.backgroundImage, backgroundSize: eventToKeep.style.backgroundSize, left: eventToKeep.style.left, right: eventToKeep.style.right, visibility: eventToKeep.style.visibility, width: eventToKeep.style.width, border: eventToKeep.style.border, borderColor: eventToKeep.style.borderColor, textShadow: eventToKeep.style.textShadow, }; eventToKeep.style.backgroundImage = generateGradient(colors, 10, 45); eventToKeep.style.backgroundSize = "initial"; eventToKeep.style.left = Math.min.apply( Math, positions.map((s) => s.left) ) + "px"; eventToKeep.style.right = Math.min.apply( Math, positions.map((s) => s.right) ) + "px"; eventToKeep.style.visibility = "visible"; eventToKeep.style.width = null; eventToKeep.style.border = "solid 1px #FFF"; // Clear setting color for declined events eventToKeep.querySelector('[aria-hidden="true"]').style.color = null; const computedSpanStyle = window.getComputedStyle( eventToKeep.querySelector("span") ); if (computedSpanStyle.color == "rgb(255, 255, 255)") { eventToKeep.style.textShadow = "0px 0px 2px black"; } else { eventToKeep.style.textShadow = "0px 0px 2px white"; } events.forEach((event) => { event.style.visibility = "hidden"; }); } else { const dots = eventToKeep.querySelector('[role="button"] div:first-child'); const dot = dots.querySelector("div"); dot.style.backgroundImage = generateGradient(colors, 4, 90); dot.style.width = colors.length * 4 + "px"; dot.style.borderWidth = 0; dot.style.height = "8px"; events.forEach((event) => { event.style.visibility = "hidden"; }); } } /** * Reset merged events * @param {HTMLElement[]} events * @returns {void} */ const resetMergedEvents = (events) => { events.forEach((event) => { for (var k in event.originalStyle) { event.style[k] = event.originalStyle[k]; } event.style.visibility = "visible"; }); } /** * Merge events within a given element * @param {HTMLElement} calendarElement */ const merge = (calendarElement) => { const eventSets = {}; const days = calendarElement.querySelectorAll('[role="gridcell"]'); days.forEach((day, index) => { const events = Array.from( day.querySelectorAll( '[data-eventid][role="button"], [data-eventid] [role="button"]' ) ); events.forEach((event) => { const eventTitleEls = event.querySelectorAll('[aria-hidden="true"]'); if (!eventTitleEls.length) { return; } let eventKey = Array.from(eventTitleEls) .map((el) => el.textContent) .join("") .replace(/\\s+/g, ""); eventKey = index + eventKey + event.style.height; eventSets[eventKey] = eventSets[eventKey] || []; eventSets[eventKey].push(event); }); }); Object.values(eventSets).forEach((events) => { if (events.length > 1) { mergeEventElements(events); } else { resetMergedEvents(events); } }); } /** * Observer callback which merges events on specific mutations * @param {MutationRecord[]} mutations List of observed mutation records * @return {void} */ const init = (mutations) => { mutations && mutations .map((mutation) => mutation.addedNodes[0] || mutation.target) .filter( (node) => node.matches && node.matches('[role="main"], [role="dialog"], [role="grid"]') ) .map(merge); } const observer = new MutationObserver(init); observer.observe(document.querySelector("body"), { childList: true, subtree: true, attributes: true, }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址