Lobster Keys

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);


/***/ })
/******/ ]);

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址