// ==UserScript== // @name rZhihu // @author nonoroazoro // @description 为知乎首页添加类似 Google Reader 的快捷键。 // @description:en Add keyboard shortcuts for Zhihu homepage. // @homepageURL https://github.com/nonoroazoro/rZhihu // @namespace https://greasyfork.org/zh-CN/scripts/30036-rzhihu // @grant none // @version 1.0.1 // @run-at document-end // @include https://www.zhihu.com/ // @downloadURL none // ==/UserScript== let started = false; let stories = []; let storyContainer = null; let current = 0; let maxIndex = -1; const ignoreList = ["INPUT", "DIV", "TEXTAREA"]; function start() { storyContainer = document.querySelector(".TopstoryMain"); observe(storyContainer, update); document.body.addEventListener("keydown", _keydownHandler); } function _keydownHandler(e) { if (!started || ignoreList.indexOf(e.target.nodeName) != -1 || e.altKey || e.shiftKey || e.ctrlKey || e.metaKey) { return; } if (e.keyCode === 74) { // press "j" _next(); } else if (e.keyCode === 75) { // press "k" _prev(); } else if (e.keyCode === 79 || e.keyCode === 13) { // press "o/enter" _toggle(); } else if (e.keyCode === 85) { // press "u" _unlike(); } } /** * flip to next story. */ function _next() { _flip(current + 1); } /** * flip to previous story. */ function _prev() { _flip(current - 1); } /** * flip to story. */ function _flip(index) { current = index; if (current < 0) { current = 0; } if (current > maxIndex) { current = maxIndex; } const offsetTop = stories[current].offsetTop; if (window.scrollY !== offsetTop) { window.scrollTo(0, offsetTop); } } /** * toggle story expand/collapse. */ function _toggle() { const story = stories[current]; const expand = story.querySelector(".is-collapsed .RichContent-inner"); if (expand) { expand.click(); } else { const collapse = story.querySelector(".ContentItem-actions > button:last-child"); collapse.click(); } } /** * toggle story unlike. */ function _unlike() { const story = stories[current]; const unlike = story.querySelector("button:first-child"); unlike.click(); } /** * update after the original zhihu story list is loaded. */ function update(mutations) { if (mutations.length > 0) { try { const index = JSON.parse(storyContainer.dataset["zaModuleInfo"])["list"]["list_size"] - 1; if (index !== maxIndex) { maxIndex = index; stories = document.querySelectorAll(".Card.TopstoryItem"); } } catch (e) { const index = storyContainer.children.length - 1; if (index !== maxIndex) { maxIndex = index; stories = storyContainer.children; } } // record script status. if (!started) { started = true; } } } function observe(element, callback) { if (element && typeof callback === "function") { (new window.MutationObserver(debounce(callback))).observe(element, { attributes: true, attributeFilter: ["data-za-module-info"] }); } } function debounce(callback, delay = 500) { let timer = null; return function (...args) { const context = this; window.clearTimeout(timer); timer = window.setTimeout(() => callback.apply(context, args), delay); }; } document.addEventListener("DOMContentLoaded", () => start());