您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
通过滚动切换 Outlook PWA 中的日历月份
// ==UserScript== // @name Outlook Calendar Scroll // @namespace https://github.com/Linho1219 // @version 1.4 // @description Scroll to switch calendar months in Outlook PWA // @author Linho1219 // @match https://outlook.live.com/* // @match https://outlook.office.com/* // @grant none // @run-at document-end // @name:zh-CN Outlook 日历滚动增强脚本 // @description:zh-CN 通过滚动切换 Outlook PWA 中的日历月份 // @homepage https://github.com/Linho1219/OutlookCalendarScroll // @supportURL https://github.com/Linho1219/OutlookCalendarScroll/issues // @icon https://outlook.live.com/favicon.ico // @license MIT // ==/UserScript== (function() { "use strict"; function hookHistoryMethod(type) { const orig = history[type]; history[type] = function(...args) { const result = orig.apply(this, args); window.dispatchEvent(new Event(type)); return result; }; } function compareStates(a, b) { if (!a.isCalendar && !b.isCalendar) return true; if (a.isCalendar && b.isCalendar) return a.view === b.view; return false; } function watch(callback) { hookHistoryMethod("pushState"); hookHistoryMethod("replaceState"); function getState() { const pathname = location.pathname; if (!pathname.startsWith("/calendar")) return { isCalendar: false }; if (pathname.startsWith("/calendar/view/day")) return { isCalendar: true, view: "day" }; if (pathname.startsWith("/calendar/view/workweek")) return { isCalendar: true, view: "workweek" }; if (pathname.startsWith("/calendar/view/week")) return { isCalendar: true, view: "week" }; if (pathname.startsWith("/calendar/view/month")) return { isCalendar: true, view: "month" }; if (pathname.startsWith("/calendar/view/")) console.warn("Unknown calendar view:", pathname); else console.log("Not view path:", pathname); return null; } let currentState = getState() || { isCalendar: false }; function listener() { const newState = getState(); if (newState && !compareStates(currentState, newState)) { currentState = newState; callback(currentState); } } window.addEventListener("popstate", listener); window.addEventListener("pushState", listener); window.addEventListener("replaceState", listener); callback(currentState); } function getCalendarDOMs() { const surface = document.querySelector( '[data-app-section="CalendarModuleSurface"]' ); const [_, prevBtn, nextBtn] = document.querySelectorAll( '[role="toolbar"] button' ); if (!surface || !prevBtn || !nextBtn) throw new Error("Calendar DOM elements not found"); const prev = () => prevBtn.click(); const next = () => nextBtn.click(); return { surface, prevBtn, nextBtn, prev, next }; } async function tryGetCalendarDOMs() { const interval = 300; return new Promise((resolve) => { const intervalHandle = setInterval(() => { try { const doms = getCalendarDOMs(); clearInterval(intervalHandle); resolve(doms); } catch (e) { console.log("Waiting for calendar DOM elements..."); } }, interval); }); } async function mount(dir) { const { surface, prev, next } = await tryGetCalendarDOMs(); return mountScrollIndicator(surface, dir, { next, prev }); } function interpretAccumulated(accumulated, TRIGGER_DISTANCE) { const positive = accumulated > 0; const abs = Math.abs(accumulated); if (abs < TRIGGER_DISTANCE) return { value: accumulated, triggered: false }; const value = (2 - TRIGGER_DISTANCE / abs) * TRIGGER_DISTANCE; return { value: positive ? value : -value, triggered: true }; } function mountScrollIndicator(surface, dir, { next, prev }) { const INDICATOR_SIZE = 50; const TRIGGER_DISTANCE = 400; const DISPLAY_DISTANCE_RATIO = 0.2; const TRIGGER_TIMEOUT = 200; const NORMAL_BG = "var(--neutralTertiary)"; const NORMAL_COLOR = "var(--black)"; const TRIGGERED_BG = "var(--themePrimary)"; const TRIGGERED_COLOR = "var(--white)"; let accumulated = 0; let timeout; const indicator = document.createElement("div"); Object.assign(indicator.style, { position: "absolute", width: `${INDICATOR_SIZE}px`, height: `${INDICATOR_SIZE}px`, borderRadius: "50%", fontSize: "20px", backgroundColor: NORMAL_BG, color: NORMAL_COLOR, fontFamily: "FluentSystemIcons", zIndex: "9999", transition: "transform 0.06s, background-color 0.1s, color 0.1s", display: "flex", alignItems: "center", justifyContent: "center", pointerEvents: "none" }); indicator.innerText = ""; if (dir === "vertical") { indicator.style.left = "50%"; surface.style.overflowY = "hidden"; } else { indicator.style.top = "50%"; surface.style.overflowX = "hidden"; } surface.style.position = "relative"; function setColor(triggered) { if (triggered) { indicator.style.backgroundColor = TRIGGERED_BG; indicator.style.color = TRIGGERED_COLOR; } else { indicator.style.backgroundColor = NORMAL_BG; indicator.style.color = NORMAL_COLOR; } } setColor(false); function setPosition(value, positive = value > 0) { const translate = -value * DISPLAY_DISTANCE_RATIO; if (dir === "vertical") { if (positive) { indicator.style.bottom = `-${INDICATOR_SIZE}px`; indicator.style.top = "auto"; } else { indicator.style.top = `-${INDICATOR_SIZE}px`; indicator.style.bottom = "auto"; } indicator.style.transform = `translateX(-50%) translateY(${translate}px)`; } else { if (positive) { indicator.style.right = `-${INDICATOR_SIZE}px`; indicator.style.left = "auto"; } else { indicator.style.left = `-${INDICATOR_SIZE}px`; indicator.style.right = "auto"; } indicator.style.transform = `translateY(-50%) translateX(${translate}px)`; } } setPosition(0); function reset(value) { accumulated = 0; setPosition(0, value > 0); setColor(false); } function trigger(value) { if (value < 0) prev(); else next(); reset(value); } surface.appendChild(indicator); function onWheel(e) { if (e.ctrlKey) return; const delta = dir === "vertical" ? e.deltaY : e.deltaX; if (!delta) return; accumulated += delta; const { triggered, value } = interpretAccumulated( accumulated, TRIGGER_DISTANCE ); setColor(triggered); setPosition(value); clearTimeout(timeout); timeout = window.setTimeout(() => { if (Math.abs(accumulated) >= TRIGGER_DISTANCE) { trigger(value); } else { reset(value); } }, TRIGGER_TIMEOUT); } surface.addEventListener("wheel", onWheel, { passive: true }); return () => { surface.removeEventListener("wheel", onWheel); indicator.remove(); }; } const dirMap = { day: "horizontal", workweek: "horizontal", week: "horizontal", month: "vertical" }; let lastDir; let canceler; async function handler(state) { if (state.isCalendar) { console.log(`Calendar view: ${state.view}`); if (lastDir !== dirMap[state.view]) { canceler?.(); lastDir = dirMap[state.view]; canceler = await mount(dirMap[state.view]); } } else { console.log("Quit calendar view"); canceler?.(); lastDir = void 0; canceler = void 0; } } window.onload = () => watch(handler); })();