mirror of
https://github.com/jheddings/obsidian-stomp.git
synced 2026-07-22 06:44:59 +00:00
Initial checkin.
This commit is contained in:
commit
55094543eb
15 changed files with 3486 additions and 0 deletions
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal 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
4
.husky/pre-commit
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx lint-staged --allow-empty
|
||||
1
.prettierignore
Normal file
1
.prettierignore
Normal file
|
|
@ -0,0 +1 @@
|
|||
test/**/*.md
|
||||
8
.prettierrc.json
Normal file
8
.prettierrc.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"printWidth": 100,
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 4,
|
||||
"useTabs": false
|
||||
}
|
||||
48
esbuild.config.mjs
Normal file
48
esbuild.config.mjs
Normal 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
44
eslint.config.js
Normal 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
10
manifest.json
Normal 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
2998
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
55
package.json
Normal file
55
package.json
Normal 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
80
src/logger.ts
Normal 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
85
src/main.ts
Normal 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
85
src/settings.ts
Normal 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
18
tsconfig.json
Normal 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
20
version.mjs
Normal 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
7
versions.json
Normal 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"
|
||||
}
|
||||
Loading…
Reference in a new issue