Skip to content

Commit ead1c55

Browse files
committed
fix: convert submodules to regular directories
1 parent 304c837 commit ead1c55

17 files changed

Lines changed: 609 additions & 2 deletions

packages/reddit-ctrl-enter-sender

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
node_modules
11+
.output
12+
stats.html
13+
stats-*.json
14+
.wxt
15+
web-ext.config.ts
16+
.env.local
17+
18+
# Editor directories and files
19+
.vscode/*
20+
!.vscode/extensions.json
21+
.idea
22+
.DS_Store
23+
*.suo
24+
*.ntvs*
25+
*.njsproj
26+
*.sln
27+
*.sw?
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Reddit Ctrl+Enter Sender
2+
3+
A userscript that enables quick submission of replies, comments, or edits on Reddit using the Ctrl+Enter (Windows/Linux) or Cmd+Enter (Mac) keyboard shortcut.
4+
5+
## Features
6+
7+
- **Universal Shortcuts**: Supports Ctrl+Enter (Windows/Linux) and Cmd+Enter (Mac)
8+
- **Smart Button Detection**: Automatically identifies submit/save buttons on the current page
9+
- **Multi-level Search**: Prioritizes buttons within the current editing area, falls back to global page buttons
10+
- **Multiple Scenarios**: Works with comment replies, edit submissions, post creation, and more
11+
- **Lightweight**: No permissions required, works entirely in the browser
12+
13+
## Installation
14+
15+
1. Install a userscript manager like [Tampermonkey](https://www.tampermonkey.net/) or [Greasemonkey](https://www.greasespot.net/)
16+
2. Download the userscript from <https://github.com/rxliuli/userscripts/releases/latest>
17+
18+
## How to Use
19+
20+
1. **Visit Reddit**: Navigate to any Reddit page (the script works on `https://www.reddit.com/**`)
21+
2. **Start Typing**: Enter your content in a comment box, reply field, or edit area
22+
3. **Use the Shortcut**: Press Ctrl+Enter (Windows/Linux) or Cmd+Enter (Mac)
23+
4. **Auto Submit**: The script automatically finds and clicks the appropriate submit/save button
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"name": "reddit-ctrl-enter-sender",
3+
"description": "Use Ctrl/Cmd+Enter to quickly send replies, comments, or save edits on Reddit.",
4+
"private": true,
5+
"version": "0.1.1",
6+
"type": "module",
7+
"scripts": {
8+
"build": "vite build",
9+
"test": "vitest run"
10+
},
11+
"devDependencies": {
12+
"@types/node": "^22.15.30",
13+
"@vitest/browser": "^3.2.2",
14+
"playwright": "^1.52.0",
15+
"typescript": "^5.8.3",
16+
"vite": "^6.3.5",
17+
"vite-node": "^3.2.4",
18+
"vite-plugin-monkey": "^7.0.3",
19+
"vite-tsconfig-paths": "^5.1.4",
20+
"vitest": "^3.2.2",
21+
"vitest-browser-react": "^0.2.0",
22+
"zx": "^8.5.5"
23+
},
24+
"packageManager": "pnpm@10.5.2+sha512.da9dc28cd3ff40d0592188235ab25d3202add8a207afbedc682220e4a0029ffbff4562102b9e6e46b4e3f9e8bd53e6d05de48544b0c57d4b0179e22c76d1199b",
25+
"dependencies": {
26+
"@medv/finder": "^4.0.2",
27+
"@rxliuli/vista": "^0.4.0"
28+
}
29+
}
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
async function main() {
2+
console.log('Reddit Ctrl+Enter Sender: Content script loaded')
3+
4+
document.addEventListener('keydown', handleKeyDown, true)
5+
6+
function handleKeyDown(event: KeyboardEvent) {
7+
if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {
8+
console.log('Reddit Ctrl+Enter Sender: Ctrl/Cmd+Enter detected')
9+
10+
event.preventDefault()
11+
event.stopPropagation()
12+
13+
findAndClickButton()
14+
}
15+
}
16+
17+
function findAndClickButton() {
18+
const activeElement = document.activeElement
19+
if (!activeElement) {
20+
console.log('Reddit Ctrl+Enter Sender: No active element found')
21+
return
22+
}
23+
24+
console.log('Reddit Ctrl+Enter Sender: Active element:', activeElement)
25+
console.log('Reddit Ctrl+Enter Sender: Active element tag:', activeElement.tagName)
26+
console.log('Reddit Ctrl+Enter Sender: Active element class:', activeElement.className)
27+
28+
// First try to find edit save button (highest priority)
29+
let submitButton = findEditSaveButton(activeElement)
30+
31+
// If no edit save button found, try to find reply submit button
32+
if (!submitButton) {
33+
submitButton = findReplySubmitButton(activeElement)
34+
}
35+
36+
// Finally try to search in the page (as a fallback)
37+
if (!submitButton) {
38+
console.log('Reddit Ctrl+Enter Sender: Trying to find submit button in page')
39+
submitButton = findSubmitButtonInPage()
40+
}
41+
42+
if (submitButton) {
43+
console.log('Reddit Ctrl+Enter Sender: Found submit button:', submitButton)
44+
console.log('Reddit Ctrl+Enter Sender: Button text:', submitButton.textContent?.trim())
45+
console.log('Reddit Ctrl+Enter Sender: Button type:', submitButton.type)
46+
console.log('Reddit Ctrl+Enter Sender: Button aria-label:', submitButton.getAttribute('aria-label'))
47+
console.log('Reddit Ctrl+Enter Sender: Clicking button...')
48+
49+
try {
50+
submitButton.click()
51+
console.log('Reddit Ctrl+Enter Sender: Button clicked successfully')
52+
} catch (error) {
53+
console.error('Reddit Ctrl+Enter Sender: Error clicking button:', error)
54+
}
55+
} else {
56+
console.log('Reddit Ctrl+Enter Sender: No submit button found')
57+
}
58+
}
59+
60+
function findEditSaveButton(activeElement: Element): HTMLButtonElement | null {
61+
// Find edit save button - usually contains "save edits", "save" etc.
62+
let composer = activeElement.closest('shreddit-composer')
63+
if (!composer) {
64+
console.log('Reddit Ctrl+Enter Sender: No shreddit-composer found in parent elements')
65+
return null
66+
}
67+
68+
console.log('Reddit Ctrl+Enter Sender: Looking for edit save button in composer')
69+
70+
// Prioritize finding explicit edit save buttons
71+
const allButtons = composer.querySelectorAll('button')
72+
for (const button of allButtons) {
73+
const buttonText = button.textContent?.toLowerCase().trim() || ''
74+
const ariaLabel = button.getAttribute('aria-label')?.toLowerCase() || ''
75+
76+
// Check if it's an edit save button
77+
if (isEditSaveButton(button as HTMLButtonElement, buttonText, ariaLabel)) {
78+
console.log('Reddit Ctrl+Enter Sender: Found edit save button:', buttonText)
79+
return button as HTMLButtonElement
80+
}
81+
}
82+
83+
return null
84+
}
85+
86+
function findReplySubmitButton(activeElement: Element): HTMLButtonElement | null {
87+
// Find reply submit button - usually used to submit new comments or replies
88+
let composer = activeElement.closest('shreddit-composer')
89+
if (!composer) {
90+
return null
91+
}
92+
93+
console.log('Reddit Ctrl+Enter Sender: Looking for reply submit button in composer')
94+
95+
const allButtons = composer.querySelectorAll('button')
96+
for (const button of allButtons) {
97+
const buttonText = button.textContent?.toLowerCase().trim() || ''
98+
const ariaLabel = button.getAttribute('aria-label')?.toLowerCase() || ''
99+
100+
// Check if it's a reply submit button
101+
if (isReplySubmitButton(button as HTMLButtonElement, buttonText, ariaLabel)) {
102+
console.log('Reddit Ctrl+Enter Sender: Found reply submit button:', buttonText)
103+
return button as HTMLButtonElement
104+
}
105+
}
106+
107+
return null
108+
}
109+
110+
function isEditSaveButton(button: HTMLButtonElement, buttonText: string, ariaLabel: string): boolean {
111+
// Characteristics of edit save button
112+
const editSavePatterns = ['save edits', 'save edit', 'save changes', 'update comment']
113+
114+
// Check button text
115+
if (editSavePatterns.some((pattern) => buttonText.includes(pattern))) {
116+
return true
117+
}
118+
119+
// Check aria-label
120+
if (editSavePatterns.some((pattern) => ariaLabel.includes(pattern))) {
121+
return true
122+
}
123+
124+
// Check if button is in edit context (by parent element or other attributes)
125+
const isInEditContext = button.closest('[data-testid*="edit"], [class*="edit"], [id*="edit"]')
126+
if (isInEditContext && buttonText.includes('save')) {
127+
return true
128+
}
129+
130+
return false
131+
}
132+
133+
function isReplySubmitButton(button: HTMLButtonElement, buttonText: string, ariaLabel: string): boolean {
134+
// Characteristics of reply submit button
135+
const replySubmitPatterns = ['reply', 'comment', 'post', 'submit', 'send']
136+
137+
// Check button text
138+
if (replySubmitPatterns.some((pattern) => buttonText.includes(pattern))) {
139+
return true
140+
}
141+
142+
// Check aria-label
143+
if (replySubmitPatterns.some((pattern) => ariaLabel.includes(pattern))) {
144+
return true
145+
}
146+
147+
// Check if it's a standard submit button
148+
if (button.type === 'submit' && button.getAttribute('slot') === 'submit-button') {
149+
return true
150+
}
151+
152+
return false
153+
}
154+
155+
function findSubmitButtonInPage(): HTMLButtonElement | null {
156+
const possibleSelectors = [
157+
'button[slot="submit-button"]',
158+
'button[type="submit"]',
159+
'button',
160+
'input[type="submit"]',
161+
]
162+
163+
for (const selector of possibleSelectors) {
164+
try {
165+
const elements = document.querySelectorAll(selector)
166+
console.log(`Reddit Ctrl+Enter Sender: Found ${elements.length} elements with selector: ${selector}`)
167+
168+
for (const element of elements) {
169+
if (element instanceof HTMLButtonElement && isSubmitButton(element)) {
170+
console.log('Reddit Ctrl+Enter Sender: Found submit button with selector:', selector)
171+
return element
172+
}
173+
}
174+
} catch (e) {
175+
console.log(`Reddit Ctrl+Enter Sender: Selector ${selector} not supported, continuing...`)
176+
continue
177+
}
178+
}
179+
180+
return null
181+
}
182+
183+
function isSubmitButton(button: HTMLButtonElement): boolean {
184+
const text = button.textContent?.toLowerCase().trim() || ''
185+
const classes = button.className.toLowerCase()
186+
const type = button.type.toLowerCase()
187+
const slot = button.getAttribute('slot')
188+
189+
console.log('Reddit Ctrl+Enter Sender: Checking button:', {
190+
text,
191+
classes,
192+
type,
193+
slot,
194+
tagName: button.tagName,
195+
})
196+
197+
if (type === 'submit') {
198+
console.log('Reddit Ctrl+Enter Sender: Button is type="submit"')
199+
return true
200+
}
201+
202+
const submitTexts = ['save edits', 'reply', 'comment', 'post', 'submit', 'send', 'save']
203+
if (submitTexts.some((submitText) => text.includes(submitText))) {
204+
console.log('Reddit Ctrl+Enter Sender: Button text matches submit pattern:', text)
205+
return true
206+
}
207+
208+
if (classes.includes('button-primary') || classes.includes('submit') || classes.includes('primary')) {
209+
console.log('Reddit Ctrl+Enter Sender: Button has primary/submit class')
210+
return true
211+
}
212+
213+
if (slot === 'submit-button') {
214+
console.log('Reddit Ctrl+Enter Sender: Button has slot="submit-button"')
215+
return true
216+
}
217+
218+
if (button.offsetParent === null || button.disabled) {
219+
console.log('Reddit Ctrl+Enter Sender: Button is not visible or disabled')
220+
return false
221+
}
222+
223+
console.log('Reddit Ctrl+Enter Sender: Button does not match submit criteria')
224+
return false
225+
}
226+
227+
return () => {
228+
document.removeEventListener('keydown', handleKeyDown, true)
229+
console.log('Reddit Ctrl+Enter Sender: Content script unloaded')
230+
}
231+
}
232+
233+
main()
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2022",
4+
"useDefineForClassFields": true,
5+
"module": "ESNext",
6+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
7+
"skipLibCheck": true,
8+
9+
"moduleResolution": "bundler",
10+
"allowImportingTsExtensions": true,
11+
"verbatimModuleSyntax": true,
12+
"moduleDetection": "force",
13+
"noEmit": true,
14+
15+
"strict": true,
16+
"noUnusedLocals": true,
17+
"noUnusedParameters": true,
18+
"erasableSyntaxOnly": true,
19+
"noFallthroughCasesInSwitch": true,
20+
"noUncheckedSideEffectImports": true
21+
},
22+
"include": ["src"]
23+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { defineConfig } from 'vite'
2+
import monkey from 'vite-plugin-monkey'
3+
4+
export default defineConfig({
5+
plugins: [
6+
monkey({
7+
entry: 'src/userscript.ts',
8+
userscript: {
9+
name: 'Reddit Ctrl+Enter Sender',
10+
namespace: 'https://rxliuli.com',
11+
description: 'Use Ctrl/Cmd+Enter to quickly send replies, comments, or save edits on Reddit.',
12+
match: ['https://www.reddit.com/**'],
13+
},
14+
}),
15+
],
16+
publicDir: false,
17+
})
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { defineConfig } from 'vitest/config'
2+
import tsconfigPaths from 'vite-tsconfig-paths'
3+
4+
export default defineConfig({
5+
test: {
6+
projects: [
7+
{
8+
plugins: [tsconfigPaths()] as any,
9+
test: {
10+
exclude: ['**/*.unit.test.ts', 'node_modules/**'],
11+
browser: {
12+
enabled: true,
13+
provider: 'playwright',
14+
// https://vitest.dev/guide/browser/playwright
15+
instances: [{ browser: 'chromium', headless: true }],
16+
},
17+
},
18+
},
19+
{
20+
plugins: [tsconfigPaths()] as any,
21+
test: {
22+
include: ['**/*.unit.test.ts'],
23+
exclude: ['*.test.ts', 'node_modules/**'],
24+
},
25+
},
26+
],
27+
},
28+
})
Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)