Initial checkin.

This commit is contained in:
jheddings 2025-07-18 21:30:38 -06:00
commit 55094543eb
15 changed files with 3486 additions and 0 deletions

23
.gitignore vendored Normal file
View file

@ -0,0 +1,23 @@
# Intellij
*.iml
.idea
# npm
node_modules
# Don't include the compiled main.js file in the repo.
# They should be uploaded to GitHub releases instead.
main.js
obsidian-chopro-*.zip
# Exclude sourcemaps
*.map
# obsidian
data.json
# Exclude temp and system files
.DS_Store
*.swp
*~

4
.husky/pre-commit Executable file
View file

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged --allow-empty

1
.prettierignore Normal file
View file

@ -0,0 +1 @@
test/**/*.md

8
.prettierrc.json Normal file
View file

@ -0,0 +1,8 @@
{
"semi": true,
"singleQuote": false,
"printWidth": 100,
"trailingComma": "es5",
"tabWidth": 4,
"useTabs": false
}

48
esbuild.config.mjs Normal file
View file

@ -0,0 +1,48 @@
import esbuild from "esbuild";
import process from "process";
import builtins from "builtin-modules";
const banner = `/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin
*/
`;
const prod = process.argv[2] === "production";
const context = await esbuild.context({
banner: {
js: banner,
},
entryPoints: ["src/main.ts"],
bundle: true,
external: [
"obsidian",
"electron",
"@codemirror/autocomplete",
"@codemirror/collab",
"@codemirror/commands",
"@codemirror/language",
"@codemirror/lint",
"@codemirror/search",
"@codemirror/state",
"@codemirror/view",
"@lezer/common",
"@lezer/highlight",
"@lezer/lr",
...builtins,
],
format: "cjs",
target: "es2018",
logLevel: "info",
sourcemap: prod ? false : "inline",
treeShaking: true,
outfile: "main.js",
});
if (prod) {
await context.rebuild();
process.exit(0);
} else {
await context.watch();
}

44
eslint.config.js Normal file
View file

@ -0,0 +1,44 @@
const typescriptEslint = require("@typescript-eslint/eslint-plugin");
const typescriptParser = require("@typescript-eslint/parser");
module.exports = [
{
// ignore generated and build files
ignores: ["main.js", "dist/**", "node_modules/**", "*.d.ts"],
},
{
files: ["**/*.ts", "**/*.tsx"],
languageOptions: {
parser: typescriptParser,
ecmaVersion: 2021,
sourceType: "module",
parserOptions: {
project: "./tsconfig.json",
},
},
plugins: {
"@typescript-eslint": typescriptEslint,
},
rules: {
semi: "off",
"@typescript-eslint/member-delimiter-style": "off",
"@typescript-eslint/quotes": "off",
"@typescript-eslint/indent": "off",
"@typescript-eslint/comma-dangle": "off",
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-explicit-any": "warn",
},
},
{
files: ["**/*.js", "**/*.jsx"],
languageOptions: {
ecmaVersion: 2021,
sourceType: "module",
},
rules: {
semi: "off",
},
},
];

10
manifest.json Normal file
View file

@ -0,0 +1,10 @@
{
"id": "stomp",
"name": "STOMP",
"version": "0.1.0",
"minAppVersion": "0.15.0",
"description": "Controls for Reading View using foot pedals",
"author": "Jason Heddings",
"authorUrl": "https://github.com/jheddings",
"isDesktopOnly": false
}

2998
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

55
package.json Normal file
View file

@ -0,0 +1,55 @@
{
"name": "obsidian-stomp-plugin",
"version": "1.0.0",
"description": "Obsidian plugin for foot pedal integration",
"author": "Jason Heddings",
"license": "MIT",
"main": "main.js",
"scripts": {
"prepare": "husky install",
"dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"format": "prettier --write .",
"lint": "eslint --fix .",
"test": "npm run build",
"preflight": "npm run build && npm test && npm run format && npm run lint",
"release": "npm run preflight && git push origin v$npm_package_version",
"version": "node version.mjs && git add manifest.json versions.json",
"postversion": "git commit --amend -m \"obsidian-stomp-$npm_package_version\""
},
"keywords": [
"obsidian",
"plugin",
"stomp",
"pedal",
"reading",
"scroll"
],
"devDependencies": {
"@types/node": "^22.16.4",
"@typescript-eslint/eslint-plugin": "8.37.0",
"@typescript-eslint/parser": "8.37.0",
"builtin-modules": "5.0.0",
"esbuild": "0.25.7",
"eslint": "^9.31.0",
"eslint-config-prettier": "^10.1.7",
"eslint-plugin-prettier": "^5.5.3",
"husky": "^8.0.0",
"lint-staged": "^16.1.2",
"obsidian": "latest",
"prettier": "^3.6.2",
"tslib": "2.8.1",
"typescript": "5.8.3"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,jsx,ts,tsx,json,css,scss,md}": [
"prettier --write",
"eslint --fix"
]
}
}

80
src/logger.ts Normal file
View file

@ -0,0 +1,80 @@
export enum LogLevel {
DEBUG = 30,
INFO = 20,
WARN = 10,
ERROR = 0,
}
export class LoggerInstance {
private name: string;
private logLevel: LogLevel;
constructor(name: string, logLevel: LogLevel = LogLevel.ERROR) {
this.name = name;
this.logLevel = logLevel;
}
setLogLevel(level: LogLevel): void {
this.logLevel = level;
}
private shouldLog(level: LogLevel): boolean {
return level <= this.logLevel;
}
log(level: string, message: string, ...args: any[]): void {
console.log(`[${level}] Folderize:${this.name} -- ${message}`, ...args);
}
debug(message: string, ...args: any[]): void {
if (this.shouldLog(LogLevel.DEBUG)) {
this.log("DEBUG", message, ...args);
}
}
info(message: string, ...args: any[]): void {
if (this.shouldLog(LogLevel.INFO)) {
this.log("INFO", message, ...args);
}
}
warn(message: string, ...args: any[]): void {
if (this.shouldLog(LogLevel.WARN)) {
this.log("WARN", message, ...args);
}
}
error(message: string, ...args: any[]): void {
if (this.shouldLog(LogLevel.ERROR)) {
this.log("ERROR", message, ...args);
}
}
}
export class Logger {
private static loggers: Map<string, LoggerInstance> = new Map();
private static globalLogLevel: LogLevel = LogLevel.ERROR;
static getLogger(name: string): LoggerInstance {
let logger;
if (Logger.loggers.has(name)) {
logger = Logger.loggers.get(name)!;
} else {
logger = new LoggerInstance(name, Logger.globalLogLevel);
Logger.loggers.set(name, logger);
}
return logger;
}
static setGlobalLogLevel(level: LogLevel): void {
Logger.globalLogLevel = level;
for (const logger of Logger.loggers.values()) {
logger.setLogLevel(level);
}
}
static getGlobalLogLevel(): LogLevel {
return Logger.globalLogLevel;
}
}

85
src/main.ts Normal file
View file

@ -0,0 +1,85 @@
import { Plugin, Notice } from "obsidian";
import { StompSettingsTab } from "./settings";
import { Logger, LogLevel } from "./logger";
interface StompPluginSettings {
enableStompCapture: boolean;
pageUpEnabled: boolean;
pageDownEnabled: boolean;
logLevel: LogLevel;
}
const DEFAULT_SETTINGS: StompPluginSettings = {
enableStompCapture: true,
pageUpEnabled: true,
pageDownEnabled: true,
logLevel: LogLevel.ERROR,
};
export default class ObsidianStompPlugin extends Plugin {
settings: StompPluginSettings;
private logger = Logger.getLogger("StompPlugin");
async onload() {
await this.loadSettings();
// Set the global log level based on settings
Logger.setGlobalLogLevel(this.settings.logLevel);
this.addSettingTab(new StompSettingsTab(this.app, this));
this.registerDomEvent(document, "keydown", (evt: KeyboardEvent) => {
this.handleKeyDown(evt);
});
this.logger.info("STOMP Pedal Plugin loaded");
}
onunload() {
this.logger.info("STOMP Pedal Plugin unloaded");
}
handleKeyDown(evt: KeyboardEvent) {
if (!this.settings.enableStompCapture) {
return;
}
this.logger.debug(
`received event - Key: ${evt.key}, Code: ${evt.code}, Ctrl: ${evt.ctrlKey}, Alt: ${evt.altKey}, Shift: ${evt.shiftKey}`
);
if (evt.key === "PageUp" && this.settings.pageUpEnabled) {
evt.preventDefault();
evt.stopPropagation();
this.handlePageUp();
return;
}
if (evt.key === "PageDown" && this.settings.pageDownEnabled) {
evt.preventDefault();
evt.stopPropagation();
this.handlePageDown();
return;
}
}
handlePageUp() {
new Notice("🔺 STOMP: Page Up detected!", 2000);
this.logger.info("Page Up command received");
}
handlePageDown() {
new Notice("🔻 STOMP: Page Down detected!", 2000);
this.logger.info("Page Down command received");
}
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
async saveSettings() {
await this.saveData(this.settings);
// Update log level when settings are saved
Logger.setGlobalLogLevel(this.settings.logLevel);
}
}

85
src/settings.ts Normal file
View file

@ -0,0 +1,85 @@
import { App, PluginSettingTab, Setting } from "obsidian";
import ObsidianStompPlugin from "./main";
import { LogLevel } from "./logger";
export class StompSettingsTab extends PluginSettingTab {
plugin: ObsidianStompPlugin;
constructor(app: App, plugin: ObsidianStompPlugin) {
super(app, plugin);
this.plugin = plugin;
}
display(): void {
const { containerEl } = this;
containerEl.empty();
containerEl.createEl("h2", { text: "STOMP Pedal Settings" });
new Setting(containerEl)
.setName("Enable STOMP capture")
.setDesc("Enable or disable STOMP pedal command capture")
.addToggle((toggle) =>
toggle.setValue(this.plugin.settings.enableStompCapture).onChange(async (value) => {
this.plugin.settings.enableStompCapture = value;
await this.plugin.saveSettings();
})
);
containerEl.createEl("h3", { text: "Advanced Settings" });
new Setting(containerEl)
.setName("Log Level")
.setDesc("Set the logging level for debug output")
.addDropdown((dropdown) => {
dropdown.addOption(LogLevel.ERROR.toString(), "Error");
dropdown.addOption(LogLevel.WARN.toString(), "Warning");
dropdown.addOption(LogLevel.INFO.toString(), "Info");
dropdown.addOption(LogLevel.DEBUG.toString(), "Debug");
dropdown.setValue(this.plugin.settings.logLevel.toString());
dropdown.onChange(async (value) => {
this.plugin.settings.logLevel = parseInt(value) as LogLevel;
await this.plugin.saveSettings();
});
});
containerEl.createEl("h3", { text: "Key Capture Test" });
const testArea = containerEl.createEl("div", {
attr: {
style: "border: 1px solid var(--background-modifier-border); padding: 10px; margin: 10px 0; border-radius: 4px;",
},
});
testArea.createEl("p", {
text: "Press any key while focused in this area to see what key codes are being sent:",
});
const keyDisplay = testArea.createEl("div", {
attr: {
style: "background: var(--background-secondary); padding: 8px; border-radius: 4px; font-family: monospace; min-height: 40px;",
},
text: "No keys pressed yet...",
});
const testInput = testArea.createEl("input", {
type: "text",
placeholder: "Click here and press your STOMP pedal buttons",
attr: {
style: "width: 100%; padding: 8px; margin-top: 8px;",
},
});
testInput.addEventListener("keydown", (e) => {
e.preventDefault();
keyDisplay.innerHTML = `
<strong>Last key pressed:</strong><br>
Key: "${e.key}"<br>
Code: "${e.code}"<br>
Ctrl: ${e.ctrlKey}, Shift: ${e.shiftKey}, Alt: ${e.altKey}<br>
Timestamp: ${new Date().toLocaleTimeString()}
`;
});
}
}

18
tsconfig.json Normal file
View file

@ -0,0 +1,18 @@
{
"compilerOptions": {
"baseUrl": ".",
"inlineSourceMap": true,
"inlineSources": true,
"module": "ESNext",
"target": "ES6",
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
"importHelpers": true,
"declaration": true,
"outDir": "./dist",
"typeRoots": ["node_modules/@types"],
"lib": ["DOM", "ES6"]
},
"include": ["**/*.ts"]
}

20
version.mjs Normal file
View file

@ -0,0 +1,20 @@
import { readFileSync, writeFileSync } from "fs";
const targetVersion = process.env.npm_package_version;
if (!targetVersion) {
console.error("No version found in package.json");
process.exit(1);
}
console.log(`Updating version to ${targetVersion}`);
let manifest = JSON.parse(readFileSync("manifest.json", "utf8"));
manifest.version = targetVersion;
writeFileSync("manifest.json", JSON.stringify(manifest, null, 2) + "\n");
let versions = JSON.parse(readFileSync("versions.json", "utf8"));
versions[targetVersion] = manifest.minAppVersion;
writeFileSync("versions.json", JSON.stringify(versions, null, 2) + "\n");
console.log(`✓ Updated target version ${targetVersion}`);

7
versions.json Normal file
View file

@ -0,0 +1,7 @@
{
"0.1.0": "0.15.0",
"0.1.1": "0.15.0",
"0.1.2": "0.15.0",
"0.2.0": "0.15.0",
"0.2.1": "0.15.0"
}