Repeat Key

jホールドでリピート開始/リピート中にjで加速/他キーで停止

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==UserScript==
// @name        Repeat Key
// @author      to
// @namespace   https://github.com/to
// @version     0.2
// @match       https://twitter.com/*
// @description jホールドでリピート開始/リピート中にjで加速/他キーで停止
// @icon        https://www.google.com/s2/favicons?domain=twitter.com
// @license     MIT
// ==/UserScript==

(function() {
	const opt = {
		hold: 250,
		interval: 800,
		decrement: 200,
		min: 400,
		key: 'j',
	};
	// keypressイベントではcharCode(小文字)が使われる
	opt.charCode = opt.key.charCodeAt(0);
	opt.keyCode = opt.charCode - 32;

	var repeater = {
		onMouseDown: function name(params) {
			this.state.stop();
		},

		onKeyDown: function (e) {
			if (e.fake)
				return;

			// 対象外のキーか?
			if (e.keyCode != opt.keyCode) {
				this.state.stop();
				return;
			}

			if (e.repeat)
				repeater.cancelEvent(e);

			this.state.onKeyDown(e);
		},

		onKeyPress: function (e) {
			// 対象外のキーか?
			if (e.fake || e.keyCode != opt.charCode)
				return;

			this.state.onKeyPress(e);
		},

		onKeyUp: function (e) {
			// 対象外のキーか?
			if (e.fake || e.keyCode != opt.keyCode)
				return;

			this.state.onKeyUp(e);
		},

		startRepeat: function () {
			// 既存のリピートを停止する
			this.stopRepeat();
			this.timer = setInterval(() => {
				repeater.dispatchEvent();
			}, repeater.interval);
		},

		dispatchEvent: function () {
			document.dispatchEvent(repeater.createEvent('keydown'));
			document.dispatchEvent(repeater.createEvent('keypress'));
			document.dispatchEvent(repeater.createEvent('keyup'));
		},

		createEvent: function (type) {
			let event = new KeyboardEvent(type, {
				keyCode: type == 'keypress' ? opt.charCode : opt.keyCode,
				key: opt.key,
				bubbles: true,
			});

			// 生成したイベントにフラグを付加し、判別に用いる
			event.fake = true;
			return event;
		},

		stopRepeat: function () {
			clearInterval(this.timer);
		},

		cancelEvent: function (event) {
			// 他ハンドラも含めて伝播を完全に停止する
			event.preventDefault();
			event.stopPropagation();
			event.stopImmediatePropagation();
		},

		states: {
			normal: {
				onKeyDown: function (e) {
					// ホールド開始時刻を保存する
					repeater.start = Date.now();

					repeater.state = repeater.states.holding;
				},
				onKeyPress: function (e) { },
				onKeyUp: function (e) { },
				stop: function () { },
			},

			holding: {
				onKeyDown: function (e) {
					// ホールド開始から一定時間が経過していないか?
					if (Date.now() < (repeater.start + opt.hold))
						return;

					// リピート開始前にkeyupを発生させ、一連のキー押下を終わらせる
					document.dispatchEvent(repeater.createEvent('keyup'));

					repeater.interval = opt.interval;
					repeater.startRepeat();

					repeater.state = repeater.states.repeating;
				},
				onKeyPress: function e() { },
				onKeyUp: function (e) {
					repeater.state = repeater.states.normal;
				},
				stop: function () { },
			},

			repeating: {
				onKeyDown: function (e) {
					if (e.repeat)
						return;

					repeater.cancelEvent(e);

					// リピート間隔を減らす
					repeater.interval = Math.max(repeater.interval - opt.decrement, opt.min);
					repeater.startRepeat();
				},
				onKeyPress: function (e) {
					repeater.cancelEvent(e);
				},
				onKeyUp: function (e) {
					repeater.cancelEvent(e);
				},
				stop: function () {
					repeater.stopRepeat();

					repeater.state = repeater.states.normal;
				},
			},
		}
	};
	repeater.state = repeater.states.normal;

	document.addEventListener('keydown', repeater.onKeyDown.bind(repeater), true);
	document.addEventListener('keypress', repeater.onKeyPress.bind(repeater), true);
	document.addEventListener('keyup', repeater.onKeyUp.bind(repeater), true);
	document.addEventListener('mousedown', repeater.onMouseDown.bind(repeater), true);
})();