rimi-itk / Leantime keyboard shortcuts

// ==UserScript==
// @name        Leantime keyboard shortcuts
// @namespace   https://github.com/rimi-itk/UserScripts
// @description Leantime keyboard shortcuts
// @match       https://*leantime.*/*
// @match       http://leantime.local.itkdev.dk/*
// @version     0.0.1
// @author      Mikkel Ricky
// @license     MIT
// @require     https://cdn.jsdelivr.net/combine/npm/@violentmonkey/dom@2,npm/@violentmonkey/ui@0.7
// @grant       GM_addStyle
// ==/UserScript==

(async () => {
(function (web, ui) {
'use strict';

var css_248z = "";

var styles = {"keyboardShortcuts":"style-module_keyboardShortcuts__ZcRL0","table":"style-module_table__OPM9X"};
var stylesheet=".style-module_keyboardShortcuts__ZcRL0 h1,.style-module_keyboardShortcuts__ZcRL0 h2{margin:0}.style-module_table__OPM9X td:first-child{text-align:right}.style-module_table__OPM9X td:first-child kbd{color:orange}.style-module_table__OPM9X td:first-child:after{content:\" : \"}";

var w = Object.defineProperty;
var A = (a, e, t) => e in a ? w(a, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : a[e] = t;
var r = (a, e, t) => (A(a, typeof e != "symbol" ? e + "" : e, t), t);
const R = {
  /*
  eslint-disable
    @typescript-eslint/no-empty-function,
    @typescript-eslint/no-unused-vars
  */
  addEventListener: (...a) => {
  },
  removeEventListener: (...a) => {
  },
  dispatchEvent: (...a) => {
  }
  /*
  eslint-enable
    @typescript-eslint/no-empty-function,
    @typescript-eslint/no-unused-vars
  */
}, E = {
  userAgent: ""
}, K = () => typeof document < "u" ? document : R, x = () => typeof navigator < "u" ? navigator : E, q = () => x().userAgent.toLowerCase().includes("mac");
let C = !1;
const I = (a) => {
  !q() || a.key !== "Meta" || (C = !0);
}, M = (a) => {
  !C || a.key !== "Meta" || (C = !1, k());
}, b = /* @__PURE__ */ new Map(), B = (a) => {
  b.set(a.key, a);
}, W = (a) => {
  b.delete(a.key);
}, k = () => {
  for (const a of b.values()) {
    const e = new KeyboardEvent("keyup", {
      key: a.key,
      code: a.code,
      bubbles: !0,
      cancelable: !0
    });
    K().dispatchEvent(e);
  }
  b.clear();
}, L = (a) => {
  try {
    const e = () => a();
    return addEventListener("focus", e), () => {
      removeEventListener("focus", e);
    };
  } catch {
  }
}, O = (a) => {
  try {
    const e = () => {
      k(), a();
    };
    return addEventListener("blur", e), () => removeEventListener("blur", e);
  } catch {
  }
}, z = (a) => {
  try {
    const e = (t) => {
      B(t), I(t), a({
        key: t.key,
        aliases: [`@${t.code}`],
        originalEvent: t,
        composedPath: () => t.composedPath(),
        preventDefault: () => t.preventDefault()
      });
    };
    return K().addEventListener("keydown", e), () => K().removeEventListener("keydown", e);
  } catch {
  }
}, T = (a) => {
  try {
    const e = (t) => {
      W(t), M(t), a({
        key: t.key,
        aliases: [`@${t.code}`],
        originalEvent: t,
        composedPath: () => t.composedPath(),
        preventDefault: () => t.preventDefault()
      });
    };
    return K().addEventListener("keyup", e), () => K().removeEventListener("keyup", e);
  } catch {
  }
};
class S {
  constructor(e) {
    r(this, "_onPressed");
    r(this, "_onPressedWithRepeat");
    r(this, "_onReleased");
    r(this, "_isPressed");
    r(this, "_identity");
    this._isPressed = !1, this._identity = e, typeof e == "function" ? this._onPressedWithRepeat = e : (this._onPressed = e.onPressed, this._onPressedWithRepeat = e.onPressedWithRepeat, this._onReleased = e.onReleased);
  }
  get isEmpty() {
    return !this._onPressed && !this._onPressedWithRepeat && !this._onReleased;
  }
  isOwnHandler(e) {
    return this._identity === e;
  }
  executePressed(e) {
    var t, s;
    this._isPressed || (t = this._onPressed) == null || t.call(this, e), this._isPressed = !0, (s = this._onPressedWithRepeat) == null || s.call(this, e);
  }
  executeReleased(e) {
    var t;
    this._isPressed && ((t = this._onReleased) == null || t.call(this, e)), this._isPressed = !1;
  }
}
const h = class h {
  constructor(e, t, s = {}) {
    r(this, "_normalizedKeyCombo");
    r(this, "_parsedKeyCombo");
    r(this, "_handlerState");
    r(this, "_keyComboEventMapper");
    r(this, "_movingToNextSequenceAt");
    r(this, "_sequenceIndex");
    r(this, "_unitIndex");
    r(this, "_lastActiveKeyPresses");
    r(this, "_lastActiveKeyCount");
    r(this, "_isPressedWithFinalUnit");
    this._normalizedKeyCombo = h.normalizeKeyCombo(e), this._parsedKeyCombo = h.parseKeyCombo(e), this._handlerState = new S(s), this._keyComboEventMapper = t, this._movingToNextSequenceAt = 0, this._sequenceIndex = 0, this._unitIndex = 0, this._lastActiveKeyPresses = [], this._lastActiveKeyCount = 0, this._isPressedWithFinalUnit = null;
  }
  static parseKeyCombo(e) {
    if (h._parseCache[e])
      return h._parseCache[e];
    const t = e.toLowerCase();
    let s = "", i = [], n = [i], o = [n];
    const _ = [o];
    let u = !1;
    for (let c = 0; c < e.length; c += 1)
      if (t[c] === "\\")
        u = !0;
      else if ((t[c] === "+" || t[c] === ">" || t[c] === ",") && !u) {
        if (s)
          throw new Error("cannot have two operators in a row");
        s = t[c];
      } else
        t[c].match(/[^\s]/) && (s && (s === "," ? (i = [], n = [i], o = [n], _.push(o)) : s === ">" ? (i = [], n = [i], o.push(n)) : s === "+" && (i = [], n.push(i)), s = ""), u = !1, i.push(t[c]));
    const d = _.map((c) => c.map((m) => m.map((f) => f.join(""))));
    return h._parseCache[e] = d, d;
  }
  static stringifyKeyCombo(e) {
    return e.map((t) => t.map((s) => s.map((i) => i === "+" ? "\\+" : i === ">" ? "\\>" : i === "," ? "\\," : i).join("+")).join(">")).join(",");
  }
  static normalizeKeyCombo(e) {
    if (h._normalizationCache[e])
      return h._normalizationCache[e];
    const t = this.stringifyKeyCombo(this.parseKeyCombo(e));
    return h._normalizationCache[e] = t, t;
  }
  get isPressed() {
    return !!this._isPressedWithFinalUnit;
  }
  get sequenceIndex() {
    return this.isPressed ? this._parsedKeyCombo.length : this._sequenceIndex;
  }
  isOwnHandler(e) {
    return this._handlerState.isOwnHandler(e);
  }
  executePressed(e) {
    var t, s;
    !((t = this._isPressedWithFinalUnit) != null && t.has(e.key)) && !((s = e.aliases) != null && s.some((i) => {
      var n;
      return (n = this._isPressedWithFinalUnit) == null ? void 0 : n.has(i);
    })) || this._handlerState.executePressed(this._wrapEvent(this._lastActiveKeyPresses, {
      key: e.key,
      aliases: new Set(e.aliases),
      event: e
    }));
  }
  executeReleased(e) {
    var t, s;
    !((t = this._isPressedWithFinalUnit) != null && t.has(e.key)) && !((s = e.aliases) != null && s.some((i) => {
      var n;
      return (n = this._isPressedWithFinalUnit) == null ? void 0 : n.has(i);
    })) || (this._handlerState.executeReleased(this._wrapEvent(this._lastActiveKeyPresses, {
      key: e.key,
      aliases: new Set(e.aliases),
      event: e
    })), this._isPressedWithFinalUnit = null);
  }
  updateState(e, t) {
    const s = e.length, i = s < this._lastActiveKeyCount;
    this._lastActiveKeyCount = s;
    const n = this._parsedKeyCombo[this._sequenceIndex], o = n.slice(0, this._unitIndex), _ = n.slice(this._unitIndex), u = () => {
      this._movingToNextSequenceAt = 0, this._sequenceIndex = 0, this._unitIndex = 0, this._lastActiveKeyPresses.length = 0, this._handlerState.isEmpty && (this._isPressedWithFinalUnit = null);
    };
    let d = 0;
    if (i) {
      if (this._movingToNextSequenceAt === 0)
        return u();
      if (this._movingToNextSequenceAt + t < Date.now() || s !== 0)
        return;
      this._movingToNextSequenceAt = 0, this._sequenceIndex += 1, this._unitIndex = 0;
      return;
    }
    for (const c of o) {
      for (const m of c) {
        let f = !1;
        for (let l = d; l < e.length && l < d + c.length; l += 1)
          if (e[l].key === m || e[l].aliases.has(m)) {
            f = !0;
            break;
          }
        if (!f)
          return u();
      }
      d += c.length;
    }
    if (this._movingToNextSequenceAt === 0) {
      for (const c of _) {
        for (const m of c) {
          let f = !1;
          for (let l = d; l < e.length && l < d + c.length; l += 1)
            if (e[l].key === m || e[l].aliases.has(m)) {
              f = !0;
              break;
            }
          if (!f)
            return;
        }
        this._unitIndex += 1, d += c.length;
      }
      if (d < s - 1)
        return u();
      if (this._lastActiveKeyPresses[this._sequenceIndex] = e.slice(0), this._sequenceIndex < this._parsedKeyCombo.length - 1) {
        this._movingToNextSequenceAt = Date.now();
        return;
      }
      this._isPressedWithFinalUnit = new Set(n[n.length - 1]);
    }
  }
  _wrapEvent(e, t) {
    return {
      ...this._keyComboEventMapper(e, t),
      keyCombo: this._normalizedKeyCombo,
      keyEvents: e.flat().map((i) => i.event),
      finalKeyEvent: t.event
    };
  }
};
r(h, "_parseCache", {}), r(h, "_normalizationCache", {});
let y = h;
const U = 1e3;
class P {
  constructor(e = {}) {
    r(this, "sequenceTimeout");
    r(this, "_isActive");
    r(this, "_unbinder");
    r(this, "_onActiveBinder");
    r(this, "_onInactiveBinder");
    r(this, "_onKeyPressedBinder");
    r(this, "_onKeyReleasedBinder");
    r(this, "_keyComboEventMapper");
    r(this, "_selfReleasingKeys");
    r(this, "_keyRemap");
    r(this, "_handlerStates");
    r(this, "_keyComboStates");
    r(this, "_keyComboStatesArray");
    r(this, "_activeKeyPresses");
    r(this, "_activeKeyMap");
    r(this, "_watchedKeyComboStates");
    this.sequenceTimeout = U, this._isActive = !0, this._onActiveBinder = () => {
    }, this._onInactiveBinder = () => {
    }, this._onKeyPressedBinder = () => {
    }, this._onKeyReleasedBinder = () => {
    }, this._keyComboEventMapper = () => ({}), this._selfReleasingKeys = [], this._keyRemap = {}, this._handlerStates = {}, this._keyComboStates = {}, this._keyComboStatesArray = [], this._activeKeyPresses = [], this._activeKeyMap = /* @__PURE__ */ new Map(), this._watchedKeyComboStates = {}, this.bindEnvironment(e);
  }
  get pressedKeys() {
    return this._activeKeyPresses.map((e) => e.key);
  }
  bindKey(e, t) {
    var i;
    if (typeof e == "object") {
      for (const n of e)
        this.bindKey(n, t);
      return;
    }
    e = e.toLowerCase();
    const s = new S(t);
    (i = this._handlerStates)[e] ?? (i[e] = []), this._handlerStates[e].push(s);
  }
  unbindKey(e, t) {
    if (typeof e == "object") {
      for (const i of e)
        this.unbindKey(i, t);
      return;
    }
    e = e.toLowerCase();
    const s = this._handlerStates[e];
    if (s)
      if (t)
        for (let i = 0; i < s.length; i += 1)
          s[i].isOwnHandler(t) && (s.splice(i, 1), i -= 1);
      else
        s.length = 0;
  }
  bindKeyCombo(e, t) {
    var i;
    if (typeof e == "object") {
      for (const n of e)
        this.bindKeyCombo(n, t);
      return;
    }
    e = y.normalizeKeyCombo(e);
    const s = new y(e, this._keyComboEventMapper, t);
    (i = this._keyComboStates)[e] ?? (i[e] = []), this._keyComboStates[e].push(s), this._keyComboStatesArray.push(s);
  }
  unbindKeyCombo(e, t) {
    if (typeof e == "object") {
      for (const i of e)
        this.unbindKeyCombo(i, t);
      return;
    }
    e = y.normalizeKeyCombo(e);
    const s = this._keyComboStates[e];
    if (s)
      if (t) {
        for (let i = 0; i < s.length; i += 1)
          if (s[i].isOwnHandler(t)) {
            for (let n = 0; n < this._keyComboStatesArray.length; n += 1)
              this._keyComboStatesArray[n] === s[i] && (this._keyComboStatesArray.splice(n, 1), n -= 1);
            s.splice(i, 1), i -= 1;
          }
      } else
        s.length = 0;
  }
  checkKey(e) {
    return this._activeKeyMap.has(e.toLowerCase());
  }
  checkKeyCombo(e) {
    return this._ensureCachedKeyComboState(e).isPressed;
  }
  checkKeyComboSequenceIndex(e) {
    return this._ensureCachedKeyComboState(e).sequenceIndex;
  }
  bindEnvironment(e = {}) {
    this.unbindEnvironment(), this._onActiveBinder = e.onActive ?? L, this._onInactiveBinder = e.onInactive ?? O, this._onKeyPressedBinder = e.onKeyPressed ?? z, this._onKeyReleasedBinder = e.onKeyReleased ?? T, this._keyComboEventMapper = e.mapKeyComboEvent ?? (() => ({})), this._selfReleasingKeys = e.selfReleasingKeys ?? [], this._keyRemap = e.keyRemap ?? {};
    const t = this._onActiveBinder(() => {
      this._isActive = !0;
    }), s = this._onInactiveBinder(() => {
      this._isActive = !1;
    }), i = this._onKeyPressedBinder((o) => {
      this._handleKeyPress(o);
    }), n = this._onKeyReleasedBinder((o) => {
      this._handleKeyRelease(o);
    });
    this._unbinder = () => {
      t == null || t(), s == null || s(), i == null || i(), n == null || n();
    };
  }
  unbindEnvironment() {
    var e;
    (e = this._unbinder) == null || e.call(this);
  }
  _ensureCachedKeyComboState(e) {
    e = y.normalizeKeyCombo(e), this._watchedKeyComboStates[e] || (this._watchedKeyComboStates[e] = new y(e, this._keyComboEventMapper));
    const t = this._watchedKeyComboStates[e];
    return t.updateState(this._activeKeyPresses, this.sequenceTimeout), t;
  }
  _handleKeyPress(e) {
    var n;
    if (!this._isActive)
      return;
    e = {
      ...e,
      key: e.key.toLowerCase(),
      aliases: ((n = e.aliases) == null ? void 0 : n.map((o) => o.toLowerCase())) ?? []
    };
    const t = this._keyRemap[e.key];
    if (t && (e.key = t), e.aliases)
      for (let o = 0; o < e.aliases.length; o += 1) {
        const _ = this._keyRemap[e.aliases[o]];
        _ && (e.aliases[o] = _);
      }
    const s = this._handlerStates[e.key];
    if (s)
      for (const o of s)
        o.executePressed(e);
    const i = this._activeKeyMap.get(e.key);
    if (i)
      i.event = e;
    else {
      const o = {
        key: e.key,
        aliases: new Set(e.aliases),
        event: e
      };
      this._activeKeyMap.set(e.key, o), this._activeKeyPresses.push(o);
    }
    this._updateKeyComboStates();
    for (const o of this._keyComboStatesArray)
      o.executePressed(e);
  }
  _handleKeyRelease(e) {
    var i;
    e = {
      ...e,
      key: e.key.toLowerCase(),
      aliases: ((i = e.aliases) == null ? void 0 : i.map((n) => n.toLowerCase())) ?? []
    };
    const t = this._keyRemap[e.key];
    if (t && (e.key = t), e.aliases)
      for (let n = 0; n < e.aliases.length; n += 1) {
        const o = this._keyRemap[e.aliases[n]];
        o && (e.aliases[n] = o);
      }
    const s = this._handlerStates[e.key];
    if (s)
      for (const n of s)
        n.executeReleased(e);
    if (this._activeKeyMap.has(e.key)) {
      this._activeKeyMap.delete(e.key);
      for (let n = 0; n < this._activeKeyPresses.length; n += 1)
        if (this._activeKeyPresses[n].key === e.key) {
          this._activeKeyPresses.splice(n, 1), n -= 1;
          break;
        }
    }
    this._tryReleaseSelfReleasingKeys(), this._updateKeyComboStates();
    for (const n of this._keyComboStatesArray)
      n.executeReleased(e);
  }
  _updateKeyComboStates() {
    for (const e of this._keyComboStatesArray)
      e.updateState(this._activeKeyPresses, this.sequenceTimeout);
  }
  _tryReleaseSelfReleasingKeys() {
    for (const e of this._activeKeyPresses)
      for (const t of this._selfReleasingKeys)
        e.key === t && this._handleKeyRelease(e.event);
  }
}
let g, v;
const F = (a) => {
  v = a ?? new P(g);
}, p = () => (v || F(), v), N = (...a) => p().bindKey(...a), G = (...a) => p().bindKeyCombo(...a); y.normalizeKeyCombo; y.stringifyKeyCombo; y.parseKeyCombo;

var _tmpl$ = /*#__PURE__*/web.template(`<span>`),
  _tmpl$2 = /*#__PURE__*/web.template(`<span class=then>then`),
  _tmpl$3 = /*#__PURE__*/web.template(`<kbd>`),
  _tmpl$4 = /*#__PURE__*/web.template(`<div><h1>Keyboard shortcuts`),
  _tmpl$5 = /*#__PURE__*/web.template(`<section><h2></h2><table>`),
  _tmpl$6 = /*#__PURE__*/web.template(`<tr><td></td><td>`);
const shortcuts = [{
  title: 'Global',
  shortcuts: [{
    keys: '?',
    description: 'Open keyboard navigation help. You already knew that, right?!',
    callable: () => panel.show()
  }, {
    keys: 'Escape',
    description: 'Close this overlay',
    callable: () => {
      panel.hide();
    }
  }, {
    keys: 'g, h',
    description: 'Go home',
    path: '/'
  }, {
    keys: 'g, p',
    description: 'Show projects',
    path: '/projects/showMy'
  }, {
    keys: 'g, c',
    description: 'Show calendar',
    path: '/calendar/showMyCalendar'
  }, {
    keys: 'g, m',
    description: 'Show profile',
    path: '/users/editOwn/'
  }, {
    keys: 'g, t, w',
    description: 'Show week timesheets',
    path: '/timesheets/showMy'
  }, {
    keys: 'g, t, l',
    description: 'Show list timesheets',
    path: '/timesheets/showMyList'
  }]
}, {
  title: 'Project (@todo)',
  shortcuts: [{
    keys: 'c',
    description: 'Create new to-do'
  }]
}, {
  title: 'To-do (@todo)',
  shortcuts: [{
    keys: 't',
    description: 'Track time on current to-do'
  }]
}];
function Help() {
  const renderKeys = keys => {
    const items = keys.split(/\s*(,)\s*/g);
    return (() => {
      var _el$ = _tmpl$();
      web.insert(_el$, () => items.map(key => ',' === key ? [' ', _tmpl$2(), ' '] : (() => {
        var _el$3 = _tmpl$3();
        web.insert(_el$3, key);
        return _el$3;
      })()));
      return _el$;
    })();
  };
  return (() => {
    var _el$4 = _tmpl$4();
      _el$4.firstChild;
    web.insert(_el$4, () => shortcuts.map(section => (() => {
      var _el$6 = _tmpl$5(),
        _el$7 = _el$6.firstChild,
        _el$8 = _el$7.nextSibling;
      web.insert(_el$7, () => section.title);
      web.insert(_el$8, () => section.shortcuts.map(shortcut => (() => {
        var _el$9 = _tmpl$6(),
          _el$10 = _el$9.firstChild,
          _el$11 = _el$10.nextSibling;
        web.insert(_el$10, () => renderKeys(shortcut.keys));
        web.insert(_el$11, () => shortcut.description);
        return _el$9;
      })()));
      web.effect(() => web.className(_el$8, styles.table));
      return _el$6;
    })()), null);
    web.effect(() => web.className(_el$4, styles.keyboardShortcuts));
    return _el$4;
  })();
}

// Let's create a movable panel using @violentmonkey/ui
// @see https://violentmonkey.github.io/vm-ui/functions/getPanel.html
const panel = ui.getPanel({
  theme: 'dark',
  style: [css_248z, stylesheet].join('\n')
});
Object.assign(panel.wrapper.style, {
  top: '10vh',
  left: '10vw'
});
web.render(Help, panel.body);
const allShortcuts = [].concat(...shortcuts.map(section => section.shortcuts));
const navigate = path => {
  if (window.location.pathname !== path) {
    window.location.pathname = path;
  }
};

// Build navigate shortcuts
for (const shortcut of allShortcuts.filter(shortcut => shortcut.path)) {
  (1 === shortcut.keys.length ? N : G)(shortcut.keys, () => navigate(shortcut.path));
}

// Build callback shortcuts
for (const shortcut of allShortcuts.filter(shortcut => shortcut.callable)) {
  (1 === shortcut.keys.length ? N : G)(shortcut.keys, () => {
    shortcut.callable();
  });
}

})(await import("https://esm.sh/solid-js/web"), VM);
})();