fabiosangregorio / google-calendar-quick-duplicate

A simple Chrome Extension to quickly duplicate events on Google Calendar
https://chrome.google.com/webstore/detail/google-calendar-quick-dup/belnijodgolpgmpahmdkjbjehbobnfpd
35 stars 9 forks source link

Updates location, nothing happens #1

Closed balta2ar closed 4 years ago

balta2ar commented 4 years ago

Hi! When I click "duplicate" button, I see that location in the window updates to something like this: https://calendar.google.com/calendar/r/eventedit/duplicate/<some-long-string-with-random-characters>#duplicate, but after that nothing happens. As far as I understand, the extension awaits "Duplicate" window to pup up and clicks Save. But that doesn't happen.

EDIT: if I manually click "Options -> Duplicate", it finds the window and immediately clicks Save, which is sort of a good thing. However, this prevents from editing any events because editing window is instantly closed, event if I didn't click Duplicate.

It makes more sense to enable Edit window search until it's found, and then switch to idling state until Duplicate button is pressed again.

Maybe this extension could be implemented as a Tampermonkey script as well so that you don't have to wait for the Google review to complete before publishing.

balta2ar commented 4 years ago

Here's a userscript that works 💓

// ==UserScript==
// @name         google-calendar-automate-duplication
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Automate duplication of events
// @author       Yuri Bochkarev
// @match        https://calendar.google.com/calendar/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const DELAY = 500;
    const STYLE_ACTIVE = "margin-left: 10px; font-size: 20px; font-weight: bold; color: red;";
    const STYLE_INACTIVE = "margin-left: 10px; font-size: 20px; font-weight: normal; color: grey;";
    //var currentWeek;
    var currentDay;
    //var toggle;
    var isActive = false;

    function createToggle() {
        let header = document.querySelector('[aria-label="Calendar"]');
        let d = document.createElement('a');
        d.style = STYLE_INACTIVE;
        d.onclick = function() {
            isActive = !isActive;
            if (isActive) { this.style = STYLE_ACTIVE; }
            else { this.style = STYLE_INACTIVE; }
        }
        d.innerHTML = 'D'
        header.appendChild(d);
        return d
    }

    function getCurrentWeek() {
        for (let div of document.querySelectorAll('div')) {
            if (div.innerText.startsWith('Week ')) {
                return div.innerText;
            }
        }

        return null;
    }

    function getCurrentDay() {
        let mini = document.querySelector('[data-month]')
        if (mini) return mini.querySelector('[aria-selected=true]');
    }

    function app() {
        createToggle();

        var saveClicker = setInterval(function () {
            if (!isActive) return;
            var saveButton = document.querySelector('div[aria-label=Save]');
            if (saveButton == null) return;
            saveButton.click();
            setTimeout(function() { currentDay.click(); }, DELAY);

            //var scrollToCurrentWeek = setInterval(function() {
            //    if (currentWeek == getCurrentWeek()) { clearInterval(scrollToCurrentWeek); return; }
            //    var nextWeekButton = document.querySelector('div[aria-label="Next week"]');
            //    if (nextWeekButton) { nextWeekButton.click(); return; }
            //    clearInterval(scrollToCurrentWeek);
            //}, 500);

        }, DELAY);

        var simulateClick = function(element) {
            // Simulate mouse down
            element.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, cancelable: true, view: window }));
            // Simulate mouse release
            element.dispatchEvent(new MouseEvent("mouseup", { bubbles: true, cancelable: true, view: window }))
        }

        var optionsClicker = setInterval(function () {
            if (!isActive) return;
            var optionsButton = document.querySelector('div[aria-label=Options]');
            var duplicateButton = document.querySelector('span[aria-label=Duplicate]');

            if (optionsButton != null && duplicateButton == null) {
                optionsButton.click();
            } else if (optionsButton != null && duplicateButton != null) {
                //currentWeek = getCurrentWeek();
                currentDay = getCurrentDay();
                simulateClick(duplicateButton);
            }
        }, DELAY);
    }

    if (document.readyState === "complete" ||
        (document.readyState !== "loading" && !document.documentElement.doScroll)
       ) {
        app();
    } else {
        document.addEventListener("DOMContentLoaded", app);
    }
})();
fabiosangregorio commented 4 years ago

Hi, thank you for opening the issue. Yes, triggering the click event on some buttons is a bit tricky on Google Calendar :)

I'll look into this and see if I can reproduce the bug after my exams next week. I'll keep you posted.

balta2ar commented 4 years ago

I found this question (https://stackoverflow.com/questions/50095952/javascript-trigger-jsaction-from-chrome-console) on how to trigger a click in google's jsaction library, but there is no answer and I have no idea either (the regular click event sometimes doesn't work).

balta2ar commented 4 years ago

My awesome colleague cracked the problem and the following click simulation works with Duplicate menu entry, now it's clickable:

            // Simulate mouse down
            element.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, cancelable: true, view: window }));
            // Simulate mouse release
            element.dispatchEvent(new MouseEvent("mouseup", { bubbles: true, cancelable: true, view: window }))

The only downside that I see now is that after Save button is pressed, the app scrolls back to the current week (I usually duplicate events of the following week).

fabiosangregorio commented 4 years ago

This is awesome! I'll be happy to implement this next week . If you'd like to put together a PR for this i'll be happy to merge it.

The scroll to the current week is something I have noticed too (see #2 ). I have yet to implement a workaround for it. But maybe, with this click event dispatch, I am going to be able to trigger click event on the current day button in the mini calendar!

balta2ar commented 4 years ago

trigger click event on the current day button in the mini calendar

But that will bring you back to the current day. Since I only use "Week" layout, I guess for me it will work if I remember the current (Week X label in the top bar) and if I keep clicking "Next week" button until the current week label matches.

balta2ar commented 4 years ago

I keep clicking "Next week" button until the current week label matches

It worked just fine. Here is an excerpt:

var currentWeek;

        var saveClicker = setInterval(function () {
            var saveButton = document.querySelector('div[aria-label=Save]');
            if (saveButton == null) return;
            saveButton.click();

            var scrollToCurrentWeek = setInterval(function() {
                if (currentWeek == getCurrentWeek()) { clearInterval(scrollToCurrentWeek); return; }
                var nextWeekButton = document.querySelector('div[aria-label="Next week"]');
                if (nextWeekButton) { nextWeekButton.click(); return; }
                clearInterval(scrollToCurrentWeek);
            }, 500);

        }, 500);

...

                currentWeek = getCurrentWeek();
                simulateClick(duplicateButton);

I've also updated the full userscript. Jesus, gcalendar is usable at last!

fabiosangregorio commented 4 years ago

But that will bring you back to the current day.

I don't quite understand what do you mean here. The workflow i have in mind to fix the problem is this:

  1. Click the duplicate button
  2. The script saves the current day in the selected view (this can be accomplished by cloning the highlighted day element in the mini calendar on the left)
  3. The script opens the duplicate view, clicks save (this will jump the app back to today)
  4. The script Inserts the cloned element in the mini calendar, clicks it (this will jump back to the original week)

Here's an example of the mini calendar with the highlighted day (highlighted day is 18, current day is 4): Mini calendar

balta2ar commented 4 years ago

clicks it (this will jump back to the original week)

This is weird: I tried exactly your approach before you described it and why it didn't work for me was that the layout changed from Week to Day, thus I naturally thought that whenever you click a day on a mini sidebar, you go to Day view. But now I try it again, and Week layout is preserved, so clearly your approach if both faster and more robust! :+1:

fabiosangregorio commented 4 years ago

I will implement this next week then. :)

Just to make sure we don't waste time duplicating the work, are you planning to put together a PR for this?

balta2ar commented 4 years ago

No, I think I'll keep it as my userscript for now because it would be easier for me to hack new things if I have other ideas. Thank you for giving me inspiration!

balta2ar commented 4 years ago

I've updated my previous comment with the latest version, added your go-to-current-day approach and a nice toggle button to enable/disable duplicator functionality on the fly.

fabiosangregorio commented 4 years ago

Hi @balta2ar, I updated the extension to version 1.0.2 and fixed the duplication of the event following your suggestions. This should work correctly now. I also fixed the jumping to the current day when duplicating an event on a week other than the current (See #2 ). Could you please check if this could work for you?