Prevent Discourse from jumping after posting a reply by intercepting the reply button click and forcing shiftKey, keeping scroll position and context.
目前為
// ==UserScript==
// @name Discourse Prevent Jump on Reply
// @name:zh-CN Discourse 回复时防止跳转
// @namespace https://github.com/utags
// @homepageURL https://github.com/utags/userscripts#readme
// @supportURL https://github.com/utags/userscripts/issues
// @version 0.1.2
// @description Prevent Discourse from jumping after posting a reply by intercepting the reply button click and forcing shiftKey, keeping scroll position and context.
// @description:zh-CN 拦截回复按钮点击并强制 shiftKey,避免发帖后页面跳转,保持当前位置与上下文。
// @icon https://www.google.com/s2/favicons?sz=64&domain=meta.discourse.org
// @author Pipecraft
// @license MIT
// @match https://meta.discourse.org/*
// @match https://linux.do/*
// @match https://idcflare.com/*
// @match https://www.nodeloc.com/*
// @match https://meta.appinn.net/*
// @noframes
// @run-at document-idle
// @grant GM.getValue
// @grant GM.setValue
// ==/UserScript==
//
;(() => {
'use strict'
var SELECTOR_REPLY_BUTTON =
'.composer-action-reply .save-or-cancel button.create'
var I18N_LABEL = {
en: 'Prevent jump to latest post',
'zh-CN': '\u9632\u6B62\u8DF3\u8F6C\u5230\u6700\u65B0\u5E16\u5B50',
}
function getDiscourseLocale() {
try {
const htmlLang = (
document.documentElement.getAttribute('lang') || ''
).toLowerCase()
if (htmlLang) return htmlLang
const bodyLang =
(document.body && document.body.getAttribute('lang')) || ''
if (bodyLang) return bodyLang.toLowerCase()
const classes = (document.documentElement.className || '').toLowerCase()
const m = /\blocale-([a-z-]+)/.exec(classes)
if (m && m[1]) return m[1]
const meta =
document.querySelector('meta[name="language"]') ||
document.querySelector('meta[http-equiv="content-language"]')
const metaLang = meta && meta.content ? meta.content.toLowerCase() : ''
if (metaLang) return metaLang
} catch (e) {}
return ''
}
function getLang() {
const l =
getDiscourseLocale() || String(navigator.language || '').toLowerCase()
return l.startsWith('zh') ? 'zh-CN' : 'en'
}
var inited = /* @__PURE__ */ new WeakSet()
function register(button) {
if (!button || inited.has(button)) return
inited.add(button)
ensureToggle(button)
button.addEventListener(
'click',
(originalEvent) => {
if (!getEnabled() || originalEvent.shiftKey) return
originalEvent.stopImmediatePropagation()
originalEvent.preventDefault()
const newEvent = new MouseEvent('click', {
bubbles: originalEvent.bubbles,
cancelable: originalEvent.cancelable,
clientX: originalEvent.clientX,
clientY: originalEvent.clientY,
shiftKey: true,
altKey: originalEvent.altKey,
ctrlKey: originalEvent.ctrlKey,
metaKey: originalEvent.metaKey,
button: originalEvent.button,
buttons: originalEvent.buttons,
})
originalEvent.target.dispatchEvent(newEvent)
},
true
)
}
function scan() {
const list = document.querySelectorAll(SELECTOR_REPLY_BUTTON)
for (const b of list) register(b)
}
function getActiveReplyButton() {
const list = document.querySelectorAll(SELECTOR_REPLY_BUTTON)
return (
Array.from(list).find((b) => Boolean(b.offsetParent)) || list[0] || null
)
}
document.addEventListener(
'keydown',
(e) => {
if (
getEnabled() &&
(e.metaKey || e.ctrlKey) &&
(e.key === 'Enter' || e.code === 'Enter')
) {
e.stopImmediatePropagation()
e.preventDefault()
const btn = getActiveReplyButton()
if (btn) {
const ev = new MouseEvent('click', {
bubbles: true,
cancelable: true,
shiftKey: true,
})
btn.dispatchEvent(ev)
}
}
},
true
)
var KEY = 'dpjor_enabled:' + (location.hostname || '')
var enabledFlag = false
function getEnabled() {
return Boolean(enabledFlag)
}
async function loadEnabled() {
try {
const val = await GM.getValue(KEY, '0')
enabledFlag = val === '1' || val === true
updateToggleUI()
} catch (e) {
enabledFlag = false
}
}
function setEnabled(v) {
enabledFlag = Boolean(v)
try {
GM.setValue(KEY, v ? '1' : '0')
} catch (e) {}
}
function updateToggleUI() {
try {
for (const cb of document.querySelectorAll(
'.dpjor-toggle input[type="checkbox"]'
))
cb.checked = getEnabled()
} catch (e) {}
}
function ensureToggle(button) {
const container = button.closest('.save-or-cancel') || button.parentElement
if (!container || container.querySelector('.dpjor-toggle')) return
const label = document.createElement('label')
label.className = 'dpjor-toggle'
label.style.marginLeft = '8px'
label.style.display = 'inline-flex'
label.style.alignItems = 'center'
label.style.gap = '6px'
const cb = document.createElement('input')
cb.type = 'checkbox'
cb.checked = getEnabled()
const span = document.createElement('span')
span.textContent = I18N_LABEL[getLang()] || I18N_LABEL.en
cb.addEventListener('change', () => {
setEnabled(cb.checked)
})
label.append(cb)
label.append(span)
container.append(label)
}
void loadEnabled()
scan()
var mo = new MutationObserver(() => {
scan()
})
mo.observe(document.documentElement || document.body, {
childList: true,
subtree: true,
})
})()