Keybindings for lobste.rs
// ==UserScript== // @name Lobster Keys // @description Keybindings for lobste.rs // @namespace https://rodaine.com // @source https://github.com/rodaine/lobster-keys.git // @version 0.1.0 // @author Chris Roche <[email protected]> // @match https://lobste.rs/* // @grant none // @noframes // ==/UserScript== /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); /******/ } /******/ }; /******/ /******/ // define __esModule on exports /******/ __webpack_require__.r = function(exports) { /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); /******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; /******/ /******/ // create a fake namespace object /******/ // mode & 1: value is a module id, require it /******/ // mode & 2: merge all properties of value into the ns /******/ // mode & 4: return value when already ns object /******/ // mode & 8|1: behave like require /******/ __webpack_require__.t = function(value, mode) { /******/ if(mode & 1) value = __webpack_require__(value); /******/ if(mode & 8) return value; /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; /******/ var ns = Object.create(null); /******/ __webpack_require__.r(ns); /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); /******/ return ns; /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 0); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; /** * Story element on the document. */ class Story { constructor(el) { this._el = el; } /** * Extracts all stories in-order from `d`. * * @param d Document */ static FromDoc(d = window.document) { let els = d.getElementsByClassName('story'); let out = []; for (let el of els) { out.push(new Story(el)); } return out; } /** * Selects this story, making the keybindings apply to this Story. If a * different story was previously seelcted, that story should be * unfocused first. */ focus() { this._el.classList.add(Story.focusCls); let url = this.getAnchor(".u-url" /* URL */); if (url) { url.focus(); url.blur(); } this.scrollIntoView(); } /** * Deselects this story. */ unfocus() { this._el.classList.remove(Story.focusCls); } /** * Opens the Story's URL */ open() { this.click(".u-url" /* URL */); } /** * Opens the domain search page for the Story's hostname */ domain() { this.click(".domain" /* Domain */); } /** * Opens the Story's author's profile page. */ author() { this.click(".u-author" /* Author */); } /** * Opens the flag dropdown menu, and moves focus to the first option. */ flag() { this.click(".flagger" /* Flag */); let opts = window.document.querySelector('#downvote_why a'); opts && opts.focus(); } /** * Toggles whether or not the Story is hidden. */ hide() { this.click(".hider" /* Hide */); } /** * Toggles whether or not the Story is saved. */ save() { this.click(".saver" /* Save */); } /** * Opens the Story's comments page. */ comments() { this.click(".comments_label a" /* Comments */); } /** * Toggles the Story's upvote arrow. */ upvote() { this.click(".upvoter" /* Upvote */); } getAnchor(a) { return this._el.querySelector(a); } click(a) { let anchor = this.getAnchor(a); anchor && anchor.click(); } scrollIntoView() { let bound = this._el.getBoundingClientRect(); if (bound.top < 0 || bound.left < 0 || bound.bottom > (window.innerHeight || document.documentElement.clientHeight) || bound.right > (window.innerWidth || document.documentElement.clientWidth)) { return this._el.scrollIntoView(Story.scrollOpts); } } } /** * The class name applied to the selected [[Story]] */ Story.focusCls = 'lobster-keys-focus'; Story.scrollOpts = { block: "nearest" }; /** * Interaction controller for the keybindings */ class LobstersKeyController { /** * Attaches a controller to `d`, listening for events and injecting styles * if a [[Story]] list is detected. * * @param d Document */ constructor(d = window.document) { this.stories = Story.FromDoc(d); if (this.stories.length > 0) { document.addEventListener('keyup', (e) => { this.handleKeyUp(e); }); this.attachStyles(d); } } get index() { return this._idx; } set index(i) { if (i === undefined) { this._idx = undefined; return; } if (i < 0) { i = 0; } else if (i >= this.stories.length) { i = this.stories.length - 1; } this._idx = i; } get story() { let i = this.index; return i === undefined ? undefined : this.stories[i]; } changeStory(d) { if (this.index === undefined) { switch (d) { case 1 /* Next */: this.index = 0; break; case 0 /* Previous */: this.index = this.stories.length - 1; break; } } else { this.story && this.story.unfocus(); switch (d) { case 1 /* Next */: this.index++; break; case 0 /* Previous */: this.index--; break; } } this.story && this.story.focus(); } changePage(d) { switch (d) { case 0 /* Previous */: let prev = window.document.querySelector('.morelink a:first-child'); prev && prev.innerText.indexOf('<<') > -1 && prev.click(); break; case 1 /* Next */: let next = window.document.querySelector('.morelink a:last-child'); next && next.innerText.indexOf('>>') > -1 && next.click(); break; } } handleKeyUp(e) { switch (e.code) { case "KeyJ" /* J */: return this.changeStory(1 /* Next */); case "KeyK" /* K */: return this.changeStory(0 /* Previous */); case "BracketLeft" /* OpenBracket */: return this.changePage(0 /* Previous */); case "BracketRight" /* CloseBracket */: return this.changePage(1 /* Next */); } let story = this.story; if (story) { switch (e.code) { case "Enter" /* Enter */: return story.open(); case "KeyA" /* A */: return story.author(); case "KeyC" /* C */: return story.comments(); case "KeyD" /* D */: return story.domain(); case "KeyF" /* F */: return story.flag(); case "KeyH" /* H */: return story.hide(); case "KeyS" /* S */: return story.save(); case "KeyU" /* U */: return story.upvote(); } } } attachStyles(d) { let styles = d.createElement('style'); styles.innerHTML = `.${Story.focusCls} { background-color: #fffcd799; }`; d.body.appendChild(styles); } } new LobstersKeyController(window.document); /***/ }) /******/ ]);