Google Calendar Header and Navigation Switcher

By yooskeh Last update Feb 29, 2008 — Installed 8,585 times. Daily Installs: 14, 4, 12, 9, 15, 4, 9, 12, 12, 8, 8, 7, 4, 14, 8, 12, 9, 10, 6, 6, 10, 4, 11, 10, 9, 12, 8, 6, 9, 6, 6, 12
// ==UserScript==
// @name            Google Calendar Header and Navigation Switcher
// @namespace       http://d.hatena.ne.jp/margin/
// @description     Can switch display of the header and the navigation bar by click triangle icon on top or on left.
// @include         http://calendar.google.tld/*
// @include         https://calendar.google.tld/*
// @include         http://www.google.tld/calendar*
// @include         https://www.google.tld/calendar*
// @include         http://google.tld/calendar*
// @include         https://google.tld/calendar*
// @version         0.1.11
// @author          yooskeh
// @homepage        http://d.hatena.ne.jp/margin/
// ==/UserScript==

// class DisplaySwitcher

function DisplaySwitcher(id, selector, onSwitch) {
  this.id = id;
  this.selector = selector;
  this.onSwitch = onSwitch || function(){};

  this.style = document.createElement("style");
  this.style.type = "text/css";
  document.getElementsByTagName("head")[0].appendChild(this.style);

  this.switchButton = document.createElement("div");
  this.switchButton.id = id + "SwitchButton";
  var self = this;
  this.switchButton.addEventListener("click", function() {
    self.toggle.call(self);
  }, false);
  document.body.appendChild(this.switchButton);

  var init = GM_getValue(this.id + "Init", "last");
  GM_setValue(this.id, !(init == "last" ? GM_getValue(this.id) : init == "hide"));
  this.toggle();
}
DisplaySwitcher.prototype.isHide = function() {
  return GM_getValue(this.id);
};
DisplaySwitcher.prototype.toggle = function() {
  GM_setValue(this.id, !GM_getValue(this.id));
  var hide = this.isHide();
  this.style.innerHTML = hide ? this.selector+"{display:none;}" : "";
  this.switchButton.className = hide ? "closed" : "opened";
  setTimeout(this.onSwitch, 0);
};
DisplaySwitcher.prototype.show = function() {
  this.isHide() && this.toggle();
};
DisplaySwitcher.prototype.hide = function() {
  this.isHide() || this.toggle();
};

// initialize

window.addEventListener("load", function() {
  if (!document.getElementById("nav"))
    return;

  /*
  var onResize = false;
  for (var k in unsafeWindow) {
    var f = unsafeWindow[k];
    if (typeof f == "function" && typeof f.resizeId == "number") {
      onResize = function(){ f(f.resizeId); };
      break;
    }
  }
  if (!onResize) {
    onResize = function() {
      var mainbody = document.getElementById("mainbody");
      if (mainbody && mainbody.style.display != "none") {
        unsafeWindow._SR_backToCalendar();
      }
    }
  }
  */

  function refleshCal() {
    var m = document.getElementById("mainbody");
    if (m && m.style.display != "none") {
      unsafeWindow._SR_backToCalendar();
    }
  }

  var head = new DisplaySwitcher("head", "#topBar,#cornerBookmarks,#gbar,#gbarl,#guser,#ft+.s,.gbh",
                                 refleshCal);
  var navi = new DisplaySwitcher("navi", "#nav", function() {
    var tc_top = document.getElementById("tc_top");
    if (tc_top) {
      tc_top.style.display = "none";
      refleshCal();
      tc_top.style.display = "";
      refleshCal();
    }
  });

  var headKeyDefault = uneval({code:90,ctrl:false,shift:true});
  var naviKeyDefault = uneval({code:90,ctrl:false,shift:false});
  var noKeyboard = uneval({code:-1,ctrl:false,shift:false});

  var Key = {
    headKey: eval(GM_getValue("headKey", headKeyDefault)),
    naviKey: eval(GM_getValue("naviKey", naviKeyDefault))
  };
  GM_setValue("headKey", uneval(Key.headKey));
  GM_setValue("naviKey", uneval(Key.naviKey));

  function checkKey(e, key) {
    return e.keyCode  == key.code    &&
           e.shiftKey == !!key.shift &&
           e.ctrlKey  == !!key.ctrl;
  }

  GM_setValue("headInit", GM_getValue("headInit", "last"));
  GM_setValue("naviInit", GM_getValue("naviInit", "last"));

  document.addEventListener("keydown", function(e) {
    if (/input|textarea/i.test(e.target.tagName))
      return;
    if (e.keyCode == 191) head.show();
    if (checkKey(e, Key.headKey)) {
      head.toggle();
      e.preventDefault();
    }
    if (checkKey(e, Key.naviKey)) {
      navi.toggle();
      e.preventDefault();
    }
  }, true);

  // from hotkey.js
  function code2char(code) {
    function between(a, b) { return a <= code && code <= b; }
    var _32_40 = "Space PageUp PageDown End Home Left Up Right Down".split(" ");
    var table = {
      8  : "Back",
      9  : "Tab"  ,
      13 : "Enter",
      16 : "Shift",
      17 : "Ctrl",
      46 : "Delete"
    };
    return (
      between(65, 90)  ? String.fromCharCode(code+32) : // a-z
      between(48, 57)  ? String.fromCharCode(code) :    // 0-9
      between(96, 105) ? String.fromCharCode(code-48) : // num 0-9
      between(32, 40)  ? _32_40[code-32] :
      table.hasOwnProperty(code) ? table[code] :
      null
    );
  }

  function key2str(key) {
    var m = "";
    if (!!key.ctrl && key.code != 17) m += "Ctrl + ";
    if (!!key.shift && key.code != 16) m += "Shift + ";
    var c = code2char(key.code);
    return c ? m + c : "";
  }

  function $(id) { return document.getElementById(id); }

  function builder() {
    var table = document.createElement("table");
    table.createCaption().innerHTML = "Setting for Header and Sidebar Switcher";
    table.insertRow(-1).innerHTML = <>
      <th>Default state of header</th>
      <td>
        <select name="headInit">
          <option id="headInitShow" value="show">Show</option>
          <option id="headInitHide" value="hide">Hide</option>
          <option id="headInitLast" value="last">Same as last time</option>
        </select>
      </td>
    </>.toXMLString();
    table.insertRow(-1).innerHTML = <>
      <th>Default state of sidebar</th>
      <td>
        <select name="naviInit">
          <option id="naviInitShow" value="show">Show</option>
          <option id="naviInitHide" value="hide">Hide</option>
          <option id="naviInitLast" value="last">Same as last time</option>
        </select>
      </td>
    </>.toXMLString();
    table.insertRow(-1).innerHTML = <>
      <th>Keyboard shortcut to toggle header</th>
      <td>
        <input class="text" size="12" id="headKeyInput" value={key2str(Key.headKey)}/>
        <input type="hidden" id="headKey" name="headKey" value={uneval(Key.headKey)}/>
      </td>
    </>.toXMLString();
    table.insertRow(-1).innerHTML = <>
      <th>Keyboard shortcut to toggle sidebar</th>
      <td>
        <input class="text" size="12" id="naviKeyInput" value={key2str(Key.naviKey)}/>
        <input type="hidden" id="naviKey" name="naviKey" value={uneval(Key.naviKey)}/>
      </td>
    </>.toXMLString();

    setTimeout(function() {
      var headInit = GM_getValue("headInit");
      headInit == "show" ? $("headInitShow").selected = true :
      headInit == "hide" ? $("headInitHide").selected = true :
                           $("headInitLast").selected = true;
      var naviInit = GM_getValue("naviInit");
      naviInit == "show" ? $("naviInitShow").selected = true :
      naviInit == "hide" ? $("naviInitHide").selected = true :
                           $("naviInitLast").selected = true;
      $("headKeyInput").addEventListener("keydown", function(e) {
        var key = {code:e.keyCode,ctrl:!!e.ctrlKey,shift:!!e.shiftKey};
        e.target.value = key2str(key);
        if (!!e.target.value)
          $("headKey").value = uneval(key);
        e.preventDefault();
        e.stopPropagation();
      }, false);
      $("naviKeyInput").addEventListener("keydown", function(e) {
        var key = {code:e.keyCode,ctrl:!!e.ctrlKey,shift:!!e.shiftKey};
        e.target.value = key2str(key);
        if (!!e.target.value)
          $("naviKey").value = uneval(key);
        e.preventDefault();
        e.stopPropagation();
      }, false);
    }, 1);

    return table;
  }

  function onSave(data) {
    GM_setValue("headInit", data.headInit);
    GM_setValue("naviInit", data.naviInit);
    var hKey = !$("headKeyInput").value ? noKeyboard : data.headKey;
    var nKey = !$("naviKeyInput").value ? noKeyboard : data.naviKey;
    GM_setValue("headKey", hKey);
    GM_setValue("naviKey", nKey);
    Key.headKey = eval(hKey);
    Key.naviKey = eval(nKey);
  }

  function onCancel(data, text) {
    if (data.headInit != GM_getValue("headInit") ||
        data.naviInit != GM_getValue("naviInit") ||
        data.headKey  != GM_getValue("headKey") ||
        data.naviKey  != GM_getValue("naviKey")) {
      return confirm("Your changes have not been saved.");
    }
  }

  var setting = new SettingPanel(builder, onSave, onCancel);
  GM_registerMenuCommand("Setting for Header and Sidebar Switcher", function() {
    setting.open();
  });
  unsafeWindow.__GCalSettingPanel_addHook(function(currentId) {
    var a = document.createElement("a");
    a.addEventListener("click", function(){setting.open();}, false);
    a.innerHTML = "Header/Sidebar";
    setTimeout(function(){
      var stabs = document.getElementById("stabs");
      stabs.rows[0].insertCell(-1).appendChild(a);
    }, 300);
  });

  // Styles for Switch Button
  GM_addStyle(<><![CDATA[
    #naviSwitchButton, #headSwitchButton {
      position: absolute;
      width: 0;
      height: 0;
      border: 12px none transparent;
      -moz-border-radius:  2px;
      cursor: pointer;
      -moz-opacity: 0.3;
    }
    #naviSwitchButton:hover, #headSwitchButton:hover {
      -moz-opacity: 0.6;
    }
    #naviSwitchButton {
      top: 49%;
      left: 0;
      border-style: solid none;
    }
    #naviSwitchButton.closed {
      border-left-style: solid;
      -moz-border-left-colors: transparent blue;
    }
    #naviSwitchButton.opened {
      border-right-style: solid;
      -moz-border-right-colors: transparent blue;
    }
    #headSwitchButton {
      top: 0;
      left: 49%;
      border-style: none solid;
    }
    #headSwitchButton.closed {
      border-top-style: solid;
      -moz-border-top-colors: transparent blue;
    }
    #headSwitchButton.opened {
      border-bottom-style: solid;
      -moz-border-bottom-colors: transparent blue;
    }
  ]]></>);
}, false);

/***** class SettingPanel *****/

function SettingPanel(builder, onSave, onCancel) {
  this.builder = builder;
  this.onSave = onSave;
  this.onCancel = onCancel;

  this.container = document.createElement("div");
  this.panel = document.createElement("div");

  var back = document.createElement("div");
  var center = document.createElement("div");
  var bottom = document.createElement("div");
  var savebtn = document.createElement("input");
  var cancelbtn = document.createElement("input");

  this.container.appendChild(back);
  center.appendChild(this.panel);
  bottom.appendChild(savebtn);
  bottom.appendChild(cancelbtn);
  center.appendChild(bottom);
  this.container.appendChild(center);

  savebtn.type = cancelbtn.type = "button";
  savebtn.value = "Save";
  cancelbtn.value = "Cancel";
  var self = this;
  savebtn.addEventListener("click", function(){self.clickSave()}, false);
  cancelbtn.addEventListener("click", function(){self.clickCancel()}, false);

  back.style.cssText = <><![CDATA[
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: black;
    opacity: 0.5;
    z-index: 100;
  ]]></>.toString();

  center.style.cssText = <><![CDATA[
    position: fixed;
    max-height: 90%;
    width: auto;
    max-width: 90%;
    overflow: auto;
    top: 0;
    left: 0;
    background-color: white;
    z-index: 101;
    border: 5px solid silver;
    -moz-border-radius: 9px;
    padding: 4px;
  ]]></>.toString();

  bottom.style.cssText = <><![CDATA[
    text-align: center;
    margin-top: 3px;
    padding-top: 3px;
    border-top: 1px solid silver;
  ]]></>.toString();

  this.updatePosition = function() {
    center.style.top = Math.floor((back.clientHeight - center.clientHeight) / 2) + "px";
    center.style.left = Math.floor((back.clientWidth - center.clientWidth) / 2) + "px";
  }
  window.addEventListener("resize", this.updatePosition, false);
}
SettingPanel.prototype = {
  open: function() {
    this.panel.innerHTML = "";
    this.panel.appendChild(this.builder());
    document.body.appendChild(this.container);
    this.updatePosition();
  },
  close: function() {
    document.body.removeChild(this.container);
    this.panel.innerHTML = "";
  },
  clickSave: function() {
    var c = this.onSave(SettingPanel.getFormSetting(this.panel));
    if (c === undefined || !!c) {
      this.close();
    }
  },
  clickCancel: function() {
    var c = this.onCancel(SettingPanel.getFormSetting(this.panel));
    if (c === undefined || !!c) {
      this.close();
    }
  }
};

SettingPanel.getFormSetting = function(form) {
  var data = {}, table = SettingPanel.getFormSetting.TABLE;
  var tagName, key, value;
  Array.forEach(form.getElementsByTagName('*'), function(field) {
    tagName = field.tagName.toLowerCase();
    if (!table[tagName] || field.disabled || !field.name)
      return;
    key = field.name;
    value = table[tagName](field);
    if (!value)
      return;
    if (key in data) {
      if (!(data[key] instanceof Array))
        data[key] = [data[key]];
      data[key].push(value);
    } else {
      data[key] = value;
    }
  });
  return data;
};
SettingPanel.getFormSetting.TABLE = {
  input: function(field) {
    var type = field.type.toLowerCase();
    if (type == "checkbox" || type == "radio") {
      return field.checked ? field.value : null;
    } else {
      return field.value;
    }
  },
  textarea: function(field) {
    return field.value;
  },
  select: function(field) {
    if (field.type == "select-one") {
      var index = field.selectedIndex;
      return index >= 0 ? this.optionValue(field.options[index]) : null;
    } else {
      var values = [];
      Array.forEach(field.options, function(opt) {
        if (opt.selected)
          values.push(this.optionValue(opt));
      });
      return values;
    }
  },
  optionValue: function(opt) {
    return opt.value || opt.text;
  }
};

/*** setting hook ***/

unsafeWindow.__GCalSettingPanel_addHook = (unsafeWindow.__GCalSettingPanel_addHook || function(hook) {
  var hooks = [];
  var original = unsafeWindow._GenSettings;
  unsafeWindow[original.name] = unsafeWindow._GenSettings = function(id) {
    original.apply(this, arguments);
    if (!unsafeWindow.__GCalSettingPanel_canceled)
      hooks.forEach(function(hook){hook.call(this, id || 0)});
  };
  (unsafeWindow.__GCalSettingPanel_addHook = function(hook) {
    hooks.push(hook);
  })(hook);
});