mirror of
https://github.com/jsmorabito/obsidian-commander.git
synced 2026-07-22 06:40:31 +00:00
Merge pull request #202 from jsmorabito/eslint-fixes
ESLint cleanup: 266 errors to 0, plus bugs found along the way
This commit is contained in:
commit
474ba044dc
57 changed files with 8511 additions and 1756 deletions
|
|
@ -1,4 +0,0 @@
|
|||
npm node_modules
|
||||
build
|
||||
scripts/
|
||||
main.js
|
||||
25
.eslintrc
25
.eslintrc
|
|
@ -1,25 +0,0 @@
|
|||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"env": { "node": true },
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"no-unused-vars": "error",
|
||||
"@typescript-eslint/no-unused-vars": ["error", { "args": "none" }],
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"no-prototype-builtins": "off",
|
||||
"@typescript-eslint/no-empty-function": ["error"],
|
||||
"semi": ["error", "always"],
|
||||
"@typescript-eslint/class-literal-property-style": ["error", "fields"],
|
||||
"@typescript-eslint/explicit-function-return-type": ["error"],
|
||||
"@typescript-eslint/explicit-member-accessibility": ["error"]
|
||||
}
|
||||
}
|
||||
89
eslint.config.mts
Normal file
89
eslint.config.mts
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import tseslint from 'typescript-eslint';
|
||||
import obsidianmd from 'eslint-plugin-obsidianmd';
|
||||
import globals from 'globals';
|
||||
import { defineConfig, globalIgnores } from 'eslint/config';
|
||||
import { fileURLToPath } from 'url';
|
||||
import path from 'path';
|
||||
|
||||
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
export default defineConfig(
|
||||
globalIgnores([
|
||||
'node_modules',
|
||||
'build',
|
||||
'scripts',
|
||||
'main.js',
|
||||
'main.css',
|
||||
'styles.css',
|
||||
'tailwind.config.js',
|
||||
]),
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
},
|
||||
parserOptions: {
|
||||
projectService: {
|
||||
allowDefaultProject: ['eslint.config.mts', 'manifest.json'],
|
||||
},
|
||||
tsconfigRootDir: dirname,
|
||||
extraFileExtensions: ['.json'],
|
||||
},
|
||||
},
|
||||
},
|
||||
...tseslint.configs.recommended,
|
||||
{
|
||||
rules: {
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
'no-prototype-builtins': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'error',
|
||||
'@typescript-eslint/class-literal-property-style': ['error', 'fields'],
|
||||
'@typescript-eslint/explicit-function-return-type': 'error',
|
||||
'@typescript-eslint/explicit-member-accessibility': 'error',
|
||||
},
|
||||
},
|
||||
...obsidianmd.configs.recommended,
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
rules: {
|
||||
// Preact discards the return value of JSX event handlers at
|
||||
// runtime (unlike React's synthetic events), so async onClick
|
||||
// handlers are safe here - only check non-attribute misuse
|
||||
// (forEach callbacks, addEventListener, Promise executors, etc).
|
||||
'@typescript-eslint/no-misused-promises': [
|
||||
'error',
|
||||
{ checksVoidReturn: { attributes: false } },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['src/main.ts'],
|
||||
rules: {
|
||||
// Renaming this command's id would break existing users' saved hotkeys
|
||||
'obsidianmd/commands/no-command-in-command-id': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['eslint.config.mts', 'vitest.config.ts', 'src/__tests__/**'],
|
||||
rules: {
|
||||
// These only run under Node (eslint/vitest CLI), never bundled
|
||||
// into main.js or loaded inside Obsidian, so the mobile/runtime
|
||||
// compatibility rationale behind these rules doesn't apply here.
|
||||
'obsidianmd/no-nodejs-modules': 'off',
|
||||
'obsidianmd/no-global-this': 'off',
|
||||
'obsidianmd/prefer-create-el': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['package.json'],
|
||||
rules: {
|
||||
// builtin-modules, node-fetch, and npm-run-all are flagged as
|
||||
// having more modern replacements. They're dev-only build
|
||||
// tooling (used from scripts/, never bundled into main.js), so
|
||||
// swapping them carries build-breakage risk for no end-user
|
||||
// benefit. Deferred to a dedicated follow-up rather than bundled
|
||||
// into this lint-cleanup branch.
|
||||
'depend/ban-dependencies': 'off',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
|
@ -68,6 +68,7 @@
|
|||
"Hide": "Skrýt",
|
||||
"Hide other Commands": "Skrýt ostatní příkazy",
|
||||
"Double click to enter custom value": "Dvakrát klikněte pro zadání vlastní hodnoty",
|
||||
"Restore default": "Restore default",
|
||||
"Choose custom spacing for Command Buttons": "Vyberte vlastní odsazení pro tlačítka příkazů",
|
||||
"Change the spacing between commands. You can set different values on mobile and desktop.": "Změna odsazení mezi příkazy. Můžete nastavit různé hodnoty na mobilních a stolních zařízeních.",
|
||||
"Warning": "Varování",
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@
|
|||
"Hide": "Verstecken",
|
||||
"Hide other Commands": "Andere Befehle verstecken",
|
||||
"Double click to enter custom value": "Doppelklicken um eigenen Wert einzutragen",
|
||||
"Restore default": "Restore default",
|
||||
"Choose custom spacing for Command Buttons": "Wähle den Abstand zwischen Befehlen",
|
||||
"Change the spacing between commands. You can set different values on mobile and desktop.": "Verändert den Abstand zwischen Befehlen.",
|
||||
"Warning": "Achtung",
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@
|
|||
"Hide": "Hide",
|
||||
"Hide other Commands": "Hide other Commands",
|
||||
"Double click to enter custom value": "Double click to enter custom value",
|
||||
"Restore default": "Restore default",
|
||||
"Choose custom spacing for Command Buttons": "Choose custom spacing for Command Buttons",
|
||||
"Change the spacing between commands. You can set different values on mobile and desktop.": "Change the spacing between commands.",
|
||||
"Warning": "Warning",
|
||||
|
|
|
|||
|
|
@ -68,10 +68,11 @@
|
|||
"Hide": "Masquer",
|
||||
"Hide other Commands": "Masquer les autres commandes",
|
||||
"Double click to enter custom value": "Double-cliquez pour entrer une valeur personnalisée",
|
||||
"Restore default": "Restore default",
|
||||
"Choose custom spacing for Command Buttons": "Choisissez un espacement personnalisé pour les boutons de commande",
|
||||
"Change the spacing between commands.": "Modifier l'espacement entre les commandes.",
|
||||
"Change the spacing between commands. You can set different values on mobile and desktop.": "Modifier l'espacement entre les commandes.",
|
||||
"Warning": "Avertissement",
|
||||
"As of Obsidian 0.16.0 you need to explicitly enable the Tab Title Bar. Once enabled, you might need to restart Obsidian.": "À partir d'Obsidian 0.16.0, vous devez activer explicitement la barre de titre des onglets. Une fois activée, il se peut que vous deviez redémarrer Obsidian.",
|
||||
"As of Obsidian 0.16.0 you need to explicitly enable the View Header.": "À partir d'Obsidian 0.16.0, vous devez activer explicitement la barre de titre des onglets. Une fois activée, il se peut que vous deviez redémarrer Obsidian.",
|
||||
"Open Appearance Settings": "Ouvrir les paramètres d'apparence",
|
||||
"Explorer": "Explorateur"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@
|
|||
"Hide": "Sakrij",
|
||||
"Hide other Commands": "Sakrij druge naredbe",
|
||||
"Double click to enter custom value": "Dvostruki klik za unos prilagođene vrijednosti",
|
||||
"Restore default": "Restore default",
|
||||
"Choose custom spacing for Command Buttons": "Odaberi prilagođeni razmak za gumbe naredbi",
|
||||
"Change the spacing between commands. You can set different values on mobile and desktop.": "Promijeni razmak između naredbi.",
|
||||
"Warning": "Upozorenje",
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@
|
|||
"Hide": "Verberg",
|
||||
"Hide other Commands": "Verberg andere Commando's",
|
||||
"Double click to enter custom value": "Dubbel klik om een aangepaste waarde in te vullen",
|
||||
"Restore default": "Restore default",
|
||||
"Choose custom spacing for Command Buttons": "Kies aangepaste regelafstand voor Commando Knoppen",
|
||||
"Change the spacing between commands. You can set different values on mobile and desktop.": "Verander regelafstand tussen Commando's. Dit kan verschillen tussen mobiel en dekstop.",
|
||||
"Warning": "Waarschuwing",
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@
|
|||
"Hide": "Спрятать",
|
||||
"Hide other Commands": "Спрятать другие команды",
|
||||
"Double click to enter custom value": "Дважды щелкните, чтобы ввести пользовательское значение",
|
||||
"Restore default": "Restore default",
|
||||
"Choose custom spacing for Command Buttons": "Выберите пользовательский интервал между командными кнопками",
|
||||
"Change the spacing between commands. You can set different values on mobile and desktop.": "Измените расстояние между командами.",
|
||||
"Warning": "Предупреждение",
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@
|
|||
"Hide": "Dölj",
|
||||
"Hide other Commands": "Dölj andra kommandon",
|
||||
"Double click to enter custom value": "Dubbelklicka för att fylla i valfritt värde",
|
||||
"Restore default": "Restore default",
|
||||
"Choose custom spacing for Command Buttons": "Välj valfri distans mellan kommandoknappar",
|
||||
"Change the spacing between commands. You can set different values on mobile and desktop.": "Ändra distans mellan komandon. Du har möjlighet att sätta olika värden för stationära och mobila enheter.",
|
||||
"Warning": "Varning",
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@
|
|||
"Hide": "Ẩm",
|
||||
"Hide other Commands": "Ẩn các lệnh khác",
|
||||
"Double click to enter custom value": "Nhấn đúp(x2) để thêm giá trị tuỳ chỉnh",
|
||||
"Restore default": "Restore default",
|
||||
"Choose custom spacing for Command Buttons": "Chọn độ giãn cách giữa các nút",
|
||||
"Change the spacing between commands. You can set different values on mobile and desktop.": "Thay đổi khoảng các giữa các lệnh",
|
||||
"Warning": "Cảnh báo",
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@
|
|||
"Hide": "隐藏",
|
||||
"Hide other Commands": "隐藏其余命令",
|
||||
"Double click to enter custom value": "双击以添加自定义值",
|
||||
"Restore default": "Restore default",
|
||||
"Choose custom spacing for Command Buttons": "为命令按钮选择自定义间距",
|
||||
"Change the spacing between commands. You can set different values on mobile and desktop.": "改变命令之间的间距。你可以为移动和桌面设备设置不同的值。",
|
||||
"Warning": "警告",
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@
|
|||
"Hide": "隱藏",
|
||||
"Hide other Commands": "隱藏其他命令",
|
||||
"Double click to enter custom value": "連按兩下以輸入自訂值",
|
||||
"Restore default": "Restore default",
|
||||
"Choose custom spacing for Command Buttons": "選擇命令按鈕的自訂間距",
|
||||
"Change the spacing between commands. You can set different values on mobile and desktop.": "變更命令之間的間距。您可以為行動和桌面裝置設定不同的值。",
|
||||
"Warning": "警告",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "cmdr",
|
||||
"name": "Commander",
|
||||
"version": "0.5.5",
|
||||
"minAppVersion": "1.4.0",
|
||||
"minAppVersion": "1.4.4",
|
||||
"description": "Customize your workspace by adding commands everywhere, create Macros and supercharge your mobile toolbar.",
|
||||
"author": "jsmorabito & phibr0",
|
||||
"authorUrl": "https://github.com/phibr0",
|
||||
|
|
|
|||
9067
package-lock.json
generated
9067
package-lock.json
generated
File diff suppressed because it is too large
Load diff
21
package.json
21
package.json
|
|
@ -11,7 +11,10 @@
|
|||
"build:css": "npx tailwindcss -i ./main.css -o ./styles.css --minify",
|
||||
"build": "npm run build:esbuild && npm run build:css",
|
||||
"dev:css": "npx tailwindcss -i ./main.css -o ./styles.css --watch",
|
||||
"dev": "npm-run-all --parallel dev:*"
|
||||
"dev": "npm-run-all --parallel dev:*",
|
||||
"lint": "eslint .",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "jsmorabito & phibr0",
|
||||
|
|
@ -25,24 +28,32 @@
|
|||
"@codemirror/search": "^6.2.3",
|
||||
"@codemirror/state": "^6.1.4",
|
||||
"@codemirror/view": "^6.6.0",
|
||||
"@eslint/js": "^9.39.4",
|
||||
"@eslint/json": "0.14.0",
|
||||
"@types/canvas-confetti": "^1.6.0",
|
||||
"@types/node": "^18.11.9",
|
||||
"@types/react": "^18.0.25",
|
||||
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
||||
"@typescript-eslint/parser": "^5.45.0",
|
||||
"@vitest/coverage-v8": "^4.1.9",
|
||||
"builtin-modules": "^3.3.0",
|
||||
"codemirror": "^6.0.1",
|
||||
"codemirror6-themes": "^0.1.2",
|
||||
"esbuild": "^0.28.1",
|
||||
"esbuild-plugin-alias": "^0.2.1",
|
||||
"esbuild-sass-plugin": "3.3.1",
|
||||
"eslint": "^9.39.4",
|
||||
"eslint-plugin-obsidianmd": "^0.4.1",
|
||||
"globals": "^17.6.0",
|
||||
"jiti": "^2.6.1",
|
||||
"jsdom": "^29.1.1",
|
||||
"node-fetch": "^3.3.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"obsidian": "latest",
|
||||
"sass-embedded": "^1.71.1",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^4.9.3"
|
||||
"typescript": "^4.9.3",
|
||||
"typescript-eslint": "^8.59.1",
|
||||
"vitest": "^4.1.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"array-move": "^4.0.0",
|
||||
|
|
@ -58,7 +69,7 @@
|
|||
"immutable": "^4.3.8",
|
||||
"js-yaml": "^4.1.1",
|
||||
"micromatch": "^4.0.8",
|
||||
"minimatch": "^3.1.5",
|
||||
"minimatch": "^9.0.5",
|
||||
"nanoid": "^3.3.8",
|
||||
"picomatch": "^2.3.2",
|
||||
"postcss": "^8.5.10",
|
||||
|
|
|
|||
3
src/__tests__/__mocks__/l10n.ts
Normal file
3
src/__tests__/__mocks__/l10n.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export default function t(str: string): string {
|
||||
return str;
|
||||
}
|
||||
49
src/__tests__/__mocks__/obsidian.ts
Normal file
49
src/__tests__/__mocks__/obsidian.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// Minimal Obsidian API mock for unit tests
|
||||
|
||||
// moment is a global in Obsidian; provide a minimal stub
|
||||
(globalThis as Record<string, unknown>).moment = { locale: (): string => "en" };
|
||||
|
||||
export const Platform = {
|
||||
isDesktop: true,
|
||||
isMobile: false,
|
||||
};
|
||||
|
||||
export function setIcon(el: HTMLElement, icon: string): void {
|
||||
el.setAttribute("data-icon", icon);
|
||||
}
|
||||
|
||||
export function requireApiVersion(_version: string): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
export function getIconIds(): string[] {
|
||||
return ["check", "x", "star", "home"];
|
||||
}
|
||||
|
||||
export class Plugin {}
|
||||
export class Modal {}
|
||||
export class Setting {}
|
||||
export class FuzzySuggestModal<T> {
|
||||
public app: unknown;
|
||||
protected declare items: T[];
|
||||
public constructor(app: unknown) { this.app = app; }
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function -- minimal test double, no-op is intentional
|
||||
public open(): void {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function -- minimal test double, no-op is intentional
|
||||
public close(): void {}
|
||||
}
|
||||
export class SuggestModal<T> {
|
||||
public app: unknown;
|
||||
protected declare items: T[];
|
||||
public constructor(app: unknown) { this.app = app; }
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function -- minimal test double, no-op is intentional
|
||||
public open(): void {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function -- minimal test double, no-op is intentional
|
||||
public close(): void {}
|
||||
}
|
||||
export class PluginSettingTab {
|
||||
public app: unknown;
|
||||
public plugin: unknown;
|
||||
public containerEl: HTMLElement = document.createElement("div");
|
||||
public constructor(app: unknown, plugin: unknown) { this.app = app; this.plugin = plugin; }
|
||||
}
|
||||
91
src/__tests__/executeMacro.test.ts
Normal file
91
src/__tests__/executeMacro.test.ts
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import { describe, it, expect, vi } from "vitest";
|
||||
import { Action } from "../types";
|
||||
import type { Macro } from "../types";
|
||||
import type CommanderPlugin from "../main";
|
||||
import CommanderPluginClass from "../main";
|
||||
|
||||
function buildPlugin(macros: Macro[]): CommanderPlugin {
|
||||
const executeCommandById = vi.fn();
|
||||
const plugin = {
|
||||
settings: { macros },
|
||||
app: {
|
||||
commands: { executeCommandById },
|
||||
},
|
||||
} as unknown as CommanderPlugin;
|
||||
plugin.executeMacro = CommanderPluginClass.prototype.executeMacro.bind(
|
||||
plugin
|
||||
) as CommanderPlugin["executeMacro"];
|
||||
return plugin;
|
||||
}
|
||||
|
||||
describe("executeMacro", () => {
|
||||
it("throws when macro id does not exist", async () => {
|
||||
const plugin = buildPlugin([]);
|
||||
await expect(plugin.executeMacro(99)).rejects.toThrow("Macro not found");
|
||||
});
|
||||
|
||||
it("executes COMMAND actions by calling executeCommandById", async () => {
|
||||
const macro: Macro = {
|
||||
name: "test",
|
||||
icon: "check",
|
||||
macro: [
|
||||
{ action: Action.COMMAND, commandId: "editor:undo" },
|
||||
{ action: Action.COMMAND, commandId: "editor:redo" },
|
||||
],
|
||||
};
|
||||
const plugin = buildPlugin([macro]);
|
||||
await plugin.executeMacro(0);
|
||||
const spy = plugin.app.commands.executeCommandById as ReturnType<typeof vi.fn>;
|
||||
expect(spy).toHaveBeenCalledTimes(2);
|
||||
expect(spy).toHaveBeenNthCalledWith(1, "editor:undo");
|
||||
expect(spy).toHaveBeenNthCalledWith(2, "editor:redo");
|
||||
});
|
||||
|
||||
it("executes LOOP action the correct number of times", async () => {
|
||||
const macro: Macro = {
|
||||
name: "loop-test",
|
||||
icon: "star",
|
||||
macro: [{ action: Action.LOOP, times: 3, commandId: "some:command" }],
|
||||
};
|
||||
const plugin = buildPlugin([macro]);
|
||||
await plugin.executeMacro(0);
|
||||
const spy = plugin.app.commands.executeCommandById as ReturnType<typeof vi.fn>;
|
||||
expect(spy).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it("handles DELAY action without throwing", async () => {
|
||||
const macro: Macro = {
|
||||
name: "delay-test",
|
||||
icon: "home",
|
||||
macro: [{ action: Action.DELAY, delay: 0 }],
|
||||
};
|
||||
const plugin = buildPlugin([macro]);
|
||||
await expect(plugin.executeMacro(0)).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("handles EDITOR action without throwing", async () => {
|
||||
const macro: Macro = {
|
||||
name: "editor-test",
|
||||
icon: "home",
|
||||
macro: [{ action: Action.EDITOR }],
|
||||
};
|
||||
const plugin = buildPlugin([macro]);
|
||||
await expect(plugin.executeMacro(0)).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("executes mixed action sequence in correct order", async () => {
|
||||
const macro: Macro = {
|
||||
name: "mixed",
|
||||
icon: "check",
|
||||
macro: [
|
||||
{ action: Action.COMMAND, commandId: "cmd-1" },
|
||||
{ action: Action.DELAY, delay: 0 },
|
||||
{ action: Action.COMMAND, commandId: "cmd-2" },
|
||||
],
|
||||
};
|
||||
const plugin = buildPlugin([macro]);
|
||||
await plugin.executeMacro(0);
|
||||
const spy = plugin.app.commands.executeCommandById as ReturnType<typeof vi.fn>;
|
||||
expect(spy.mock.calls.map((c: string[]) => c[0])).toEqual(["cmd-1", "cmd-2"]);
|
||||
});
|
||||
});
|
||||
68
src/__tests__/injectIcons.test.ts
Normal file
68
src/__tests__/injectIcons.test.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
import { injectIcons } from "../util";
|
||||
import type CommanderPlugin from "../main";
|
||||
import type { AdvancedToolbarSettings } from "../types";
|
||||
|
||||
function makePlugin(commands: Record<string, { icon?: string }>): CommanderPlugin {
|
||||
return {
|
||||
app: { commands: { commands } },
|
||||
} as unknown as CommanderPlugin;
|
||||
}
|
||||
|
||||
function makeSettings(
|
||||
mappedIcons: AdvancedToolbarSettings["mappedIcons"]
|
||||
): AdvancedToolbarSettings {
|
||||
return {
|
||||
rowHeight: 48,
|
||||
rowCount: 1,
|
||||
spacing: 0,
|
||||
buttonWidth: 48,
|
||||
columnLayout: false,
|
||||
mappedIcons,
|
||||
tooltips: false,
|
||||
heightOffset: 0,
|
||||
};
|
||||
}
|
||||
|
||||
describe("injectIcons", () => {
|
||||
it("sets the icon on a matching command", () => {
|
||||
const commands = { "my:command": { icon: "old-icon" } };
|
||||
const settings = makeSettings([{ iconID: "star", commandID: "my:command" }]);
|
||||
injectIcons(settings, makePlugin(commands));
|
||||
expect(commands["my:command"].icon).toBe("star");
|
||||
});
|
||||
|
||||
it("removes mappedIcon entry when command does not exist", () => {
|
||||
const commands = {};
|
||||
const settings = makeSettings([{ iconID: "star", commandID: "missing:command" }]);
|
||||
injectIcons(settings, makePlugin(commands));
|
||||
expect(settings.mappedIcons).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("leaves unrelated mapped icons intact", () => {
|
||||
const commands = {
|
||||
"cmd-a": { icon: "" },
|
||||
"cmd-b": { icon: "" },
|
||||
};
|
||||
const settings = makeSettings([
|
||||
{ iconID: "check", commandID: "cmd-a" },
|
||||
{ iconID: "x", commandID: "cmd-b" },
|
||||
]);
|
||||
injectIcons(settings, makePlugin(commands));
|
||||
expect(commands["cmd-a"].icon).toBe("check");
|
||||
expect(commands["cmd-b"].icon).toBe("x");
|
||||
expect(settings.mappedIcons).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("only prunes the missing command and keeps valid ones", () => {
|
||||
const commands = { "exists:cmd": { icon: "" } };
|
||||
const settings = makeSettings([
|
||||
{ iconID: "star", commandID: "missing:cmd" },
|
||||
{ iconID: "home", commandID: "exists:cmd" },
|
||||
]);
|
||||
injectIcons(settings, makePlugin(commands));
|
||||
expect(settings.mappedIcons).toHaveLength(1);
|
||||
expect(settings.mappedIcons[0].commandID).toBe("exists:cmd");
|
||||
expect(commands["exists:cmd"].icon).toBe("home");
|
||||
});
|
||||
});
|
||||
43
src/__tests__/isModeActive.test.ts
Normal file
43
src/__tests__/isModeActive.test.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
import { isModeActive } from "../util";
|
||||
import type CommanderPlugin from "../main";
|
||||
|
||||
function makePlugin(isMobile: boolean, appId = "desktop-app"): CommanderPlugin {
|
||||
return {
|
||||
app: { isMobile, appId },
|
||||
} as unknown as CommanderPlugin;
|
||||
}
|
||||
|
||||
describe("isModeActive", () => {
|
||||
it('returns true for mode "any" on desktop', () => {
|
||||
expect(isModeActive("any", makePlugin(false))).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true for mode "any" on mobile', () => {
|
||||
expect(isModeActive("any", makePlugin(true))).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true for mode "desktop" when not mobile', () => {
|
||||
expect(isModeActive("desktop", makePlugin(false))).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for mode "desktop" when on mobile', () => {
|
||||
expect(isModeActive("desktop", makePlugin(true))).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true for mode "mobile" when on mobile', () => {
|
||||
expect(isModeActive("mobile", makePlugin(true))).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for mode "mobile" when on desktop', () => {
|
||||
expect(isModeActive("mobile", makePlugin(false))).toBe(false);
|
||||
});
|
||||
|
||||
it("returns true when mode matches the appId", () => {
|
||||
expect(isModeActive("my-vault", makePlugin(false, "my-vault"))).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false when mode does not match appId and is not any/mobile/desktop", () => {
|
||||
expect(isModeActive("other-vault", makePlugin(false, "my-vault"))).toBe(false);
|
||||
});
|
||||
});
|
||||
62
src/__tests__/locales.test.ts
Normal file
62
src/__tests__/locales.test.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
import { readdirSync, readFileSync } from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const LOCALE_DIR = path.resolve(__dirname, "../../locale");
|
||||
|
||||
function loadJson(file: string): Record<string, string> {
|
||||
return JSON.parse(
|
||||
readFileSync(path.join(LOCALE_DIR, file), "utf-8")
|
||||
) as Record<string, string>;
|
||||
}
|
||||
|
||||
// Extract all {{placeholder}} tokens from a string
|
||||
function placeholders(str: string): string[] {
|
||||
return [...str.matchAll(/\{\{[^}]+\}\}/g)].map((m) => m[0]).sort();
|
||||
}
|
||||
|
||||
const en = loadJson("en.json");
|
||||
const enKeys = Object.keys(en);
|
||||
|
||||
const localeFiles = readdirSync(LOCALE_DIR).filter(
|
||||
(f) => f.endsWith(".json") && f !== "en.json"
|
||||
);
|
||||
|
||||
// Only validate locales that have been filled in (not empty stubs)
|
||||
const filledLocales = localeFiles.filter((f) => {
|
||||
const data = loadJson(f);
|
||||
return Object.keys(data).length > 0;
|
||||
});
|
||||
|
||||
describe("locale completeness (non-empty locales only)", () => {
|
||||
it.each(filledLocales)("%s has no missing keys", (file) => {
|
||||
const data = loadJson(file);
|
||||
const missing = enKeys.filter((k) => !(k in data));
|
||||
expect(missing, `Missing keys in ${file}`).toEqual([]);
|
||||
});
|
||||
|
||||
it.each(filledLocales)("%s has no extra keys not in en.json", (file) => {
|
||||
const data = loadJson(file);
|
||||
const extra = Object.keys(data).filter((k) => !(k in en));
|
||||
expect(extra, `Extra keys in ${file}`).toEqual([]);
|
||||
});
|
||||
|
||||
it.each(filledLocales)("%s preserves all {{placeholders}}", (file) => {
|
||||
const data = loadJson(file);
|
||||
const mismatches: string[] = [];
|
||||
for (const key of enKeys) {
|
||||
if (!(key in data)) continue;
|
||||
const expected = placeholders(en[key]);
|
||||
const actual = placeholders(data[key]);
|
||||
if (JSON.stringify(expected) !== JSON.stringify(actual)) {
|
||||
mismatches.push(
|
||||
`"${key}": expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
expect(mismatches, `Placeholder mismatches in ${file}`).toEqual([]);
|
||||
});
|
||||
});
|
||||
2
src/__tests__/setup.ts
Normal file
2
src/__tests__/setup.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
(globalThis as Record<string, unknown>).moment = { locale: (): string => "en" };
|
||||
export {};
|
||||
79
src/__tests__/styles.test.ts
Normal file
79
src/__tests__/styles.test.ts
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
import { describe, it, expect, beforeEach } from "vitest";
|
||||
import { updateStyles, removeStyles } from "../util";
|
||||
import type { AdvancedToolbarSettings } from "../types";
|
||||
|
||||
const defaultSettings: AdvancedToolbarSettings = {
|
||||
rowHeight: 48,
|
||||
rowCount: 1,
|
||||
spacing: 4,
|
||||
buttonWidth: 48,
|
||||
columnLayout: false,
|
||||
mappedIcons: [],
|
||||
tooltips: false,
|
||||
heightOffset: 0,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
document.body.className = "";
|
||||
document.body.removeAttribute("style");
|
||||
});
|
||||
|
||||
describe("updateStyles", () => {
|
||||
it("sets CSS custom properties on document.body", () => {
|
||||
updateStyles(defaultSettings);
|
||||
const s = document.body.style;
|
||||
expect(s.getPropertyValue("--at-button-height")).toBe("48px");
|
||||
expect(s.getPropertyValue("--at-button-width")).toBe("48px");
|
||||
expect(s.getPropertyValue("--at-row-count")).toBe("1");
|
||||
expect(s.getPropertyValue("--at-spacing")).toBe("4px");
|
||||
expect(s.getPropertyValue("--at-offset")).toBe("0px");
|
||||
});
|
||||
|
||||
it("adds AT-row class when columnLayout is false", () => {
|
||||
updateStyles({ ...defaultSettings, columnLayout: false });
|
||||
expect(document.body.classList.contains("AT-row")).toBe(true);
|
||||
expect(document.body.classList.contains("AT-column")).toBe(false);
|
||||
});
|
||||
|
||||
it("adds AT-column class when columnLayout is true", () => {
|
||||
updateStyles({ ...defaultSettings, columnLayout: true });
|
||||
expect(document.body.classList.contains("AT-column")).toBe(true);
|
||||
expect(document.body.classList.contains("AT-row")).toBe(false);
|
||||
});
|
||||
|
||||
it("adds AT-multirow when rowCount > 1", () => {
|
||||
updateStyles({ ...defaultSettings, rowCount: 3 });
|
||||
expect(document.body.classList.contains("AT-multirow")).toBe(true);
|
||||
});
|
||||
|
||||
it("does not add AT-multirow when rowCount === 1", () => {
|
||||
updateStyles(defaultSettings);
|
||||
expect(document.body.classList.contains("AT-multirow")).toBe(false);
|
||||
});
|
||||
|
||||
it("adds AT-no-toolbar when rowCount === 0", () => {
|
||||
updateStyles({ ...defaultSettings, rowCount: 0 });
|
||||
expect(document.body.classList.contains("AT-no-toolbar")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("removeStyles", () => {
|
||||
it("removes CSS custom properties set by updateStyles", () => {
|
||||
updateStyles(defaultSettings);
|
||||
removeStyles();
|
||||
const s = document.body.style;
|
||||
expect(s.getPropertyValue("--at-button-height")).toBe("");
|
||||
expect(s.getPropertyValue("--at-button-width")).toBe("");
|
||||
expect(s.getPropertyValue("--at-row-count")).toBe("");
|
||||
expect(s.getPropertyValue("--at-spacing")).toBe("");
|
||||
expect(s.getPropertyValue("--at-offset")).toBe("");
|
||||
});
|
||||
|
||||
it("removes layout classes set by updateStyles", () => {
|
||||
updateStyles({ ...defaultSettings, rowCount: 2, columnLayout: true });
|
||||
removeStyles();
|
||||
expect(document.body.classList.contains("AT-multirow")).toBe(false);
|
||||
expect(document.body.classList.contains("AT-row")).toBe(false);
|
||||
expect(document.body.classList.contains("AT-column")).toBe(false);
|
||||
});
|
||||
});
|
||||
22
src/main.ts
22
src/main.ts
|
|
@ -43,7 +43,7 @@ export default class CommanderPlugin extends Plugin {
|
|||
public async executeStartupMacros(): Promise<void> {
|
||||
this.settings.macros.forEach((macro, idx) => {
|
||||
if (macro.startup) {
|
||||
this.executeMacro(idx);
|
||||
void this.executeMacro(idx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -55,12 +55,12 @@ export default class CommanderPlugin extends Plugin {
|
|||
for (const command of macro.macro) {
|
||||
switch (command.action) {
|
||||
case Action.COMMAND: {
|
||||
await this.app.commands.executeCommandById(command.commandId);
|
||||
this.app.commands.executeCommandById(command.commandId);
|
||||
continue;
|
||||
}
|
||||
case Action.DELAY: {
|
||||
await new Promise((resolve) =>
|
||||
setTimeout(resolve, command.delay)
|
||||
window.setTimeout(resolve, command.delay)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
|
@ -69,7 +69,7 @@ export default class CommanderPlugin extends Plugin {
|
|||
}
|
||||
case Action.LOOP: {
|
||||
for (let i = 0; i < command.times; i++) {
|
||||
await this.app.commands.executeCommandById(
|
||||
this.app.commands.executeCommandById(
|
||||
command.commandId
|
||||
);
|
||||
}
|
||||
|
|
@ -129,7 +129,7 @@ export default class CommanderPlugin extends Plugin {
|
|||
updateStyles(this.settings.advancedToolbar);
|
||||
injectIcons(this.settings.advancedToolbar, this);
|
||||
|
||||
this.executeStartupMacros();
|
||||
void this.executeStartupMacros();
|
||||
|
||||
// Push saved commands into the Text Toolbar plugin if it is installed
|
||||
this.manager.textToolbarIntegration.reorder();
|
||||
|
|
@ -142,7 +142,11 @@ export default class CommanderPlugin extends Plugin {
|
|||
}
|
||||
|
||||
private async loadSettings(): Promise<void> {
|
||||
const data = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
const data = Object.assign(
|
||||
{},
|
||||
DEFAULT_SETTINGS,
|
||||
await this.loadData()
|
||||
) as CommanderSettings;
|
||||
this.settings = data;
|
||||
}
|
||||
|
||||
|
|
@ -151,11 +155,7 @@ export default class CommanderPlugin extends Plugin {
|
|||
}
|
||||
|
||||
public listActiveToolbarCommands(): string[] {
|
||||
//@ts-ignore
|
||||
const activeCommands = this.app.vault.getConfig(
|
||||
"mobileToolbarCommands"
|
||||
);
|
||||
return activeCommands;
|
||||
return this.app.vault.getConfig("mobileToolbarCommands") as string[];
|
||||
}
|
||||
|
||||
public getCommands(): Command[] {
|
||||
|
|
|
|||
|
|
@ -10,10 +10,8 @@ export default abstract class CommandManagerBase {
|
|||
this.pairs = pairArray;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
public abstract addCommand(pair: CommandIconPair): Promise<void> | void;
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
public abstract removeCommand(pair: CommandIconPair): Promise<void> | void;
|
||||
|
||||
public abstract reorder(): Promise<void> | void;
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ export default class ExplorerManager extends CommandManagerBase {
|
|||
this.plugin
|
||||
).didChooseRemove())
|
||||
) {
|
||||
this.removeCommand(pair);
|
||||
await this.removeCommand(pair);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
@ -153,7 +153,7 @@ export default class ExplorerManager extends CommandManagerBase {
|
|||
.setIcon("command")
|
||||
.onClick(async () => {
|
||||
const pair = await chooseNewCommand(this.plugin);
|
||||
this.addCommand(pair);
|
||||
await this.addCommand(pair);
|
||||
});
|
||||
})
|
||||
.addSeparator()
|
||||
|
|
@ -197,7 +197,7 @@ export default class ExplorerManager extends CommandManagerBase {
|
|||
this.plugin
|
||||
).didChooseRemove())
|
||||
) {
|
||||
this.removeCommand(pair);
|
||||
await this.removeCommand(pair);
|
||||
}
|
||||
});
|
||||
})
|
||||
|
|
@ -221,7 +221,7 @@ export default class ExplorerManager extends CommandManagerBase {
|
|||
}
|
||||
|
||||
action.addClass("cmdr-ribbon-removing");
|
||||
action.addEventListener("transitionend", async () => {
|
||||
action.addEventListener("transitionend", () => {
|
||||
action.remove();
|
||||
this.actions.delete(pair);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ export default class LeftRibbonManager extends CommandManagerBase {
|
|||
super(plugin, plugin.settings.leftRibbon);
|
||||
this.plugin = plugin;
|
||||
|
||||
this.plugin.settings.leftRibbon.forEach((pair) =>
|
||||
this.addCommand(pair, false)
|
||||
);
|
||||
this.plugin.settings.leftRibbon.forEach((pair) => {
|
||||
void this.addCommand(pair, false);
|
||||
});
|
||||
|
||||
this.plugin.app.workspace.onLayoutReady(() => {
|
||||
// if (this.plugin.settings.showAddCommand) {
|
||||
|
|
@ -36,10 +36,8 @@ export default class LeftRibbonManager extends CommandManagerBase {
|
|||
this.plugin.addRibbonIcon(pair.icon, pair.name, () =>
|
||||
this.plugin.app.commands.executeCommandById(pair.id)
|
||||
);
|
||||
// @ts-expect-error
|
||||
const nativeAction = this.plugin.app.workspace.leftRibbon.items.find(
|
||||
// @ts-expect-error
|
||||
(i) => i.icon === pair.icon && i.name === i.name
|
||||
(i) => i.icon === pair.icon && i.title === pair.name
|
||||
);
|
||||
if (nativeAction) {
|
||||
nativeAction.buttonEl.style.color =
|
||||
|
|
@ -59,23 +57,19 @@ export default class LeftRibbonManager extends CommandManagerBase {
|
|||
this.plugin.settings.leftRibbon.remove(pair);
|
||||
await this.plugin.saveSettings();
|
||||
}
|
||||
// @ts-expect-error
|
||||
const nativeAction = this.plugin.app.workspace.leftRibbon.items.find(
|
||||
// @ts-expect-error
|
||||
(i) => i.icon === pair.icon && i.name === i.name
|
||||
(i) => i.icon === pair.icon && i.title === pair.name
|
||||
);
|
||||
if (nativeAction) {
|
||||
nativeAction.buttonEl.remove();
|
||||
this.plugin.app.workspace.leftRibbon.items.remove(nativeAction);
|
||||
}
|
||||
// @ts-expect-error
|
||||
app.workspace.leftRibbon.items.remove(nativeAction);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
public reorder(): void {
|
||||
this.plugin.settings.leftRibbon.forEach((pair) => {
|
||||
this.removeCommand(pair, false);
|
||||
this.addCommand(pair, false);
|
||||
void this.removeCommand(pair, false);
|
||||
void this.addCommand(pair, false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,28 +29,24 @@ abstract class Base extends CommandManagerBase {
|
|||
}
|
||||
|
||||
// There is no state to update
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function -- no state to update
|
||||
public reorder(): void {}
|
||||
|
||||
protected addRemovableCommand(
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
this: (_item: MenuItem) => void,
|
||||
command: Command,
|
||||
cmdPair: CommandIconPair,
|
||||
plugin: CommanderPlugin,
|
||||
menu: Menu,
|
||||
commandList: CommandIconPair[]
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
): (_item: MenuItem) => void {
|
||||
return (item: MenuItem) => {
|
||||
item.dom.addClass("cmdr");
|
||||
item.dom.addClass("cmdr", "cmdr-menu-item");
|
||||
item.dom.style.color =
|
||||
cmdPair.color === "#000000" || cmdPair.color === undefined
|
||||
? "inherit"
|
||||
: cmdPair.color;
|
||||
item.setSection("cmdr");
|
||||
|
||||
item.dom.style.display = "flex";
|
||||
const optionEl = createDiv({
|
||||
cls: "cmdr-menu-more-options",
|
||||
});
|
||||
|
|
@ -99,7 +95,7 @@ abstract class Base extends CommandManagerBase {
|
|||
plugin
|
||||
).didChooseRemove())
|
||||
) {
|
||||
removeMenu();
|
||||
await removeMenu();
|
||||
}
|
||||
});
|
||||
})
|
||||
|
|
@ -120,10 +116,10 @@ abstract class Base extends CommandManagerBase {
|
|||
|
||||
let isRemovable = false;
|
||||
const setNormal = (): void => {
|
||||
optionEl.style.display = "none";
|
||||
optionEl.removeClass("is-visible");
|
||||
};
|
||||
const setRemovable = (): void => {
|
||||
optionEl.style.display = "block";
|
||||
optionEl.addClass("is-visible");
|
||||
};
|
||||
const removeMenu = async (): Promise<void> => {
|
||||
item.dom.addClass("cmdr-removing");
|
||||
|
|
@ -164,9 +160,8 @@ abstract class Base extends CommandManagerBase {
|
|||
const pair = await chooseNewCommand(plugin);
|
||||
commandList.push(pair);
|
||||
await plugin.saveSettings();
|
||||
} catch (error) {
|
||||
//Do some proper handling here
|
||||
console.log(error);
|
||||
} catch {
|
||||
// User cancelled command/icon selection, nothing to do
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -200,8 +195,7 @@ export class EditorMenuCommandManager extends Base {
|
|||
continue;
|
||||
|
||||
menu.addItem(
|
||||
this.addRemovableCommand.call(
|
||||
this,
|
||||
this.addRemovableCommand(
|
||||
command,
|
||||
cmdPair,
|
||||
plugin,
|
||||
|
|
@ -256,8 +250,7 @@ export class FileMenuCommandManager extends Base {
|
|||
}
|
||||
|
||||
menu.addItem(
|
||||
this.addRemovableCommand.call(
|
||||
this,
|
||||
this.addRemovableCommand(
|
||||
command,
|
||||
cmdPair,
|
||||
plugin,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ export default class PageHeaderManager extends CommandManagerBase {
|
|||
const buttons = this.buttonsFor(leaf, true);
|
||||
if (!buttons || buttons.has(id)) return;
|
||||
|
||||
const buttonIcon = (view as ItemView).addAction(icon, name, () => {
|
||||
const buttonIcon = view.addAction(icon, name, () => {
|
||||
this.plugin.app.workspace.setActiveLeaf(leaf, { focus: true });
|
||||
this.plugin.app.commands.executeCommandById(id);
|
||||
});
|
||||
|
|
@ -45,7 +45,7 @@ export default class PageHeaderManager extends CommandManagerBase {
|
|||
.setIcon("command")
|
||||
.onClick(async () => {
|
||||
const pair = await chooseNewCommand(this.plugin);
|
||||
this.addCommand(pair);
|
||||
await this.addCommand(pair);
|
||||
});
|
||||
})
|
||||
.addSeparator()
|
||||
|
|
@ -59,7 +59,7 @@ export default class PageHeaderManager extends CommandManagerBase {
|
|||
if (newIcon && newIcon !== pair.icon) {
|
||||
pair.icon = newIcon;
|
||||
await this.plugin.saveSettings();
|
||||
this.reorder();
|
||||
await this.reorder();
|
||||
}
|
||||
});
|
||||
})
|
||||
|
|
@ -74,7 +74,7 @@ export default class PageHeaderManager extends CommandManagerBase {
|
|||
if (newName && newName !== pair.name) {
|
||||
pair.name = newName;
|
||||
await this.plugin.saveSettings();
|
||||
this.reorder();
|
||||
await this.reorder();
|
||||
}
|
||||
});
|
||||
})
|
||||
|
|
@ -89,7 +89,7 @@ export default class PageHeaderManager extends CommandManagerBase {
|
|||
this.plugin
|
||||
).didChooseRemove())
|
||||
) {
|
||||
this.removeCommand(pair);
|
||||
await this.removeCommand(pair);
|
||||
}
|
||||
});
|
||||
})
|
||||
|
|
@ -108,7 +108,7 @@ export default class PageHeaderManager extends CommandManagerBase {
|
|||
})
|
||||
);
|
||||
this.plugin.app.workspace.onLayoutReady(() =>
|
||||
setTimeout(() => this.addButtonsToAllLeaves(), 100)
|
||||
window.setTimeout(() => this.addButtonsToAllLeaves(), 100)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -118,7 +118,7 @@ export default class PageHeaderManager extends CommandManagerBase {
|
|||
if (!(view instanceof ItemView)) return;
|
||||
if (this.buttons.get(view)?.has(id)) return;
|
||||
const buttonIcon = view.addAction("plus", t("Add new"), async () => {
|
||||
this.addCommand(await chooseNewCommand(this.plugin));
|
||||
await this.addCommand(await chooseNewCommand(this.plugin));
|
||||
});
|
||||
buttonIcon.addClasses(["cmdr", id]);
|
||||
if (!this.buttons.has(view)) this.buttons.set(view, new Map());
|
||||
|
|
@ -126,7 +126,7 @@ export default class PageHeaderManager extends CommandManagerBase {
|
|||
}
|
||||
|
||||
private addButtonsToAllLeaves(refresh = false): void {
|
||||
activeWindow.requestAnimationFrame(() =>
|
||||
window.requestAnimationFrame(() =>
|
||||
this.plugin.app.workspace.iterateAllLeaves((leaf) =>
|
||||
this.addButtonsToLeaf(leaf, refresh)
|
||||
)
|
||||
|
|
@ -134,7 +134,7 @@ export default class PageHeaderManager extends CommandManagerBase {
|
|||
}
|
||||
|
||||
private removeButtonsFromAllLeaves(): void {
|
||||
activeWindow.requestAnimationFrame(() =>
|
||||
window.requestAnimationFrame(() =>
|
||||
this.plugin.app.workspace.iterateAllLeaves((leaf) =>
|
||||
this.removeButtonsFromLeaf(leaf)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ export default class StatusBarManager extends CommandManagerBase {
|
|||
const pair = await chooseNewCommand(
|
||||
this.plugin
|
||||
);
|
||||
this.addCommand(pair);
|
||||
await this.addCommand(pair);
|
||||
});
|
||||
})
|
||||
.showAtMouseEvent(event);
|
||||
|
|
@ -63,7 +63,7 @@ export default class StatusBarManager extends CommandManagerBase {
|
|||
setIcon(this.addBtn, "plus");
|
||||
this.addBtn.onclick = async (): Promise<void> => {
|
||||
const pair = await chooseNewCommand(this.plugin);
|
||||
this.addCommand(pair);
|
||||
await this.addCommand(pair);
|
||||
this.reorder();
|
||||
};
|
||||
if (this.plugin.settings.showAddCommand)
|
||||
|
|
@ -118,7 +118,7 @@ export default class StatusBarManager extends CommandManagerBase {
|
|||
this.plugin
|
||||
).didChooseRemove())
|
||||
) {
|
||||
this.removeCommand(pair);
|
||||
await this.removeCommand(pair);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
@ -143,7 +143,7 @@ export default class StatusBarManager extends CommandManagerBase {
|
|||
.setIcon("command")
|
||||
.onClick(async () => {
|
||||
const pair = await chooseNewCommand(this.plugin);
|
||||
this.addCommand(pair);
|
||||
await this.addCommand(pair);
|
||||
});
|
||||
})
|
||||
.addSeparator()
|
||||
|
|
@ -187,7 +187,7 @@ export default class StatusBarManager extends CommandManagerBase {
|
|||
this.plugin
|
||||
).didChooseRemove())
|
||||
) {
|
||||
this.removeCommand(pair);
|
||||
await this.removeCommand(pair);
|
||||
}
|
||||
});
|
||||
})
|
||||
|
|
@ -209,7 +209,7 @@ export default class StatusBarManager extends CommandManagerBase {
|
|||
}
|
||||
|
||||
action.addClass("cmdr-ribbon-removing");
|
||||
action.addEventListener("transitionend", async () => {
|
||||
action.addEventListener("transitionend", () => {
|
||||
action.remove();
|
||||
this.actions.delete(pair);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,13 +3,14 @@ import { CommandIconPair } from "src/types";
|
|||
import CommandManagerBase from "./commandManager";
|
||||
|
||||
interface TextToolbarAPI {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
setCommands(_cmds: { id: string; icon: string; name: string }[]): void;
|
||||
}
|
||||
|
||||
function getTextToolbarAPI(plugin: CommanderPlugin): TextToolbarAPI | undefined {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return (plugin.app as any).plugins?.plugins?.["text-formatting-toolbar"]?.api;
|
||||
const app = plugin.app as unknown as {
|
||||
plugins?: { plugins?: Record<string, { api?: TextToolbarAPI }> };
|
||||
};
|
||||
return app.plugins?.plugins?.["text-formatting-toolbar"]?.api;
|
||||
}
|
||||
|
||||
export default class TextToolbarIntegrationManager extends CommandManagerBase {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ export default class TitleBarManager extends CommandManagerBase {
|
|||
|
||||
private init(): void {
|
||||
this.plugin.app.workspace.onLayoutReady(async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
this.container = document.querySelector(
|
||||
".titlebar div.titlebar-button-container.mod-right"
|
||||
)!;
|
||||
|
|
@ -37,17 +36,17 @@ export default class TitleBarManager extends CommandManagerBase {
|
|||
}
|
||||
|
||||
if (isModeActive(cmd.mode, this.plugin)) {
|
||||
this.addTitleBarAction(cmd);
|
||||
await this.addTitleBarAction(cmd);
|
||||
}
|
||||
}
|
||||
this.plugin.saveSettings();
|
||||
await this.plugin.saveSettings();
|
||||
|
||||
this.plugin.register(() => this.addBtn.remove());
|
||||
this.addBtn.style.setProperty("--icon-size", `12px`);
|
||||
setIcon(this.addBtn, "plus");
|
||||
this.addBtn.onclick = async (): Promise<void> => {
|
||||
const pair = await chooseNewCommand(this.plugin);
|
||||
this.addCommand(pair);
|
||||
await this.addCommand(pair);
|
||||
this.reorder();
|
||||
};
|
||||
if (this.plugin.settings.showAddCommand)
|
||||
|
|
@ -63,7 +62,7 @@ export default class TitleBarManager extends CommandManagerBase {
|
|||
|
||||
public async addCommand(pair: CommandIconPair): Promise<void> {
|
||||
this.pairs.push(pair);
|
||||
this.addTitleBarAction(pair);
|
||||
await this.addTitleBarAction(pair);
|
||||
await this.plugin.saveSettings();
|
||||
}
|
||||
|
||||
|
|
@ -92,7 +91,7 @@ export default class TitleBarManager extends CommandManagerBase {
|
|||
this.plugin
|
||||
).didChooseRemove())
|
||||
) {
|
||||
this.removeCommand(pair);
|
||||
await this.removeCommand(pair);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
@ -117,7 +116,7 @@ export default class TitleBarManager extends CommandManagerBase {
|
|||
.setIcon("command")
|
||||
.onClick(async () => {
|
||||
const pair = await chooseNewCommand(this.plugin);
|
||||
this.addCommand(pair);
|
||||
await this.addCommand(pair);
|
||||
});
|
||||
})
|
||||
.addSeparator()
|
||||
|
|
@ -161,7 +160,7 @@ export default class TitleBarManager extends CommandManagerBase {
|
|||
this.plugin
|
||||
).didChooseRemove())
|
||||
) {
|
||||
this.removeCommand(pair);
|
||||
await this.removeCommand(pair);
|
||||
}
|
||||
});
|
||||
})
|
||||
|
|
@ -189,7 +188,7 @@ export default class TitleBarManager extends CommandManagerBase {
|
|||
}
|
||||
|
||||
action.addClass("cmdr-ribbon-removing");
|
||||
action.addEventListener("transitionend", async () => {
|
||||
action.addEventListener("transitionend", () => {
|
||||
action.remove();
|
||||
this.actions.delete(pair);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -45,6 +45,22 @@
|
|||
margin-bottom: -18px;
|
||||
}
|
||||
|
||||
.cmdr-menu-item {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.cmdr-slider .clickable-icon.is-disabled {
|
||||
opacity: 0.35;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.cmdr-advanced-toolbar-settings .extra-setting-button.is-disabled {
|
||||
opacity: 0.35;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.cmdr-menu-more-options {
|
||||
color: var(--text-muted);
|
||||
position: absolute;
|
||||
|
|
@ -52,11 +68,37 @@
|
|||
padding-top: 2px;
|
||||
transform: scale(0.9);
|
||||
transition: all 150ms ease;
|
||||
display: none;
|
||||
&.is-visible {
|
||||
display: block;
|
||||
}
|
||||
&:hover {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.cmdr-confirm-delete-modal {
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.cmdr-hide-suggestions {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.cmdr-name-input-wrapper-parent {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.cmdr-confetti-canvas {
|
||||
position: fixed;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
top: 0;
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.cmdr-mobile .cmdr-credits {
|
||||
place-content: center;
|
||||
}
|
||||
|
|
|
|||
16
src/types.ts
16
src/types.ts
|
|
@ -1,13 +1,12 @@
|
|||
import { h } from "preact";
|
||||
import { Command, PluginManifest } from "obsidian";
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
export enum Action {
|
||||
COMMAND,
|
||||
DELAY,
|
||||
EDITOR,
|
||||
LOOP,
|
||||
}
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
export type MacroItem =
|
||||
| { action: Action.COMMAND; commandId: string }
|
||||
|
|
@ -63,7 +62,7 @@ export interface Tab {
|
|||
tab: h.JSX.Element;
|
||||
}
|
||||
|
||||
export type Mode = "desktop" | "any" | "mobile" | string;
|
||||
export type Mode = "desktop" | "any" | "mobile" | (string & {});
|
||||
|
||||
export interface CommandIconPair {
|
||||
id: string;
|
||||
|
|
@ -73,7 +72,6 @@ export interface CommandIconPair {
|
|||
color?: string;
|
||||
}
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
declare module "obsidian" {
|
||||
interface MenuItem {
|
||||
dom: HTMLElement;
|
||||
|
|
@ -85,6 +83,7 @@ declare module "obsidian" {
|
|||
[id: string]: Command;
|
||||
};
|
||||
executeCommandById: (id: string) => void;
|
||||
removeCommand: (id: string) => void;
|
||||
};
|
||||
plugins: {
|
||||
manifests: {
|
||||
|
|
@ -111,6 +110,11 @@ declare module "obsidian" {
|
|||
title: string;
|
||||
callback: () => void;
|
||||
}[];
|
||||
items: {
|
||||
icon: string;
|
||||
title: string;
|
||||
buttonEl: HTMLElement;
|
||||
}[];
|
||||
collapseButtonEl: HTMLElement;
|
||||
ribbonItemsEl: HTMLElement;
|
||||
addRibbonItemButton: (
|
||||
|
|
@ -128,4 +132,8 @@ declare module "obsidian" {
|
|||
interface WorkspaceLeaf {
|
||||
containerEl: HTMLElement;
|
||||
}
|
||||
|
||||
interface Vault {
|
||||
getConfig(key: string): unknown;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export default class AddCommandModal extends FuzzySuggestModal<Command> {
|
|||
this.onChooseItem = (item): void => resolve(item);
|
||||
//This is wrapped inside a setTimeout, because onClose is called before onChooseItem
|
||||
this.onClose = (): number =>
|
||||
window.setTimeout(() => reject("No Command selected"), 0);
|
||||
window.setTimeout(() => reject(new Error("No Command selected")), 0);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -62,6 +62,6 @@ export default class AddCommandModal extends FuzzySuggestModal<Command> {
|
|||
}
|
||||
|
||||
// This will be overriden anyway, but typescript complains if it's not declared
|
||||
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-empty-function
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function -- overridden by awaitSelection, declared only to satisfy the type
|
||||
public onChooseItem(item: Command, evt: MouseEvent | KeyboardEvent): void {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,14 +4,12 @@ import CommanderPlugin from "src/main";
|
|||
|
||||
export default class ChooseCustomNameModal extends SuggestModal<string> {
|
||||
public constructor(
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
private defaultName: string,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
private plugin: CommanderPlugin
|
||||
) {
|
||||
super(plugin.app);
|
||||
this.setPlaceholder(t("Use a custom name"));
|
||||
this.resultContainerEl.style.display = "none";
|
||||
this.resultContainerEl.addClass("cmdr-hide-suggestions");
|
||||
|
||||
this.setInstructions([
|
||||
{
|
||||
|
|
@ -30,13 +28,13 @@ export default class ChooseCustomNameModal extends SuggestModal<string> {
|
|||
}
|
||||
|
||||
public onOpen(): void {
|
||||
super.onOpen();
|
||||
void super.onOpen();
|
||||
|
||||
this.inputEl.value = this.defaultName;
|
||||
const wrapper = createDiv({ cls: "cmdr-name-input-wrapper" });
|
||||
this.inputEl.parentNode?.insertBefore(wrapper, this.inputEl);
|
||||
wrapper.appendChild(this.inputEl);
|
||||
wrapper.parentElement!.style.display = "block";
|
||||
wrapper.parentElement!.addClass("cmdr-name-input-wrapper-parent");
|
||||
|
||||
const btn = createEl("button", { text: t("Save"), cls: "mod-cta" });
|
||||
btn.onclick = (e): void => this.selectSuggestion(this.inputEl.value, e);
|
||||
|
|
@ -49,7 +47,7 @@ export default class ChooseCustomNameModal extends SuggestModal<string> {
|
|||
this.onChooseSuggestion = (item): void => resolve(item);
|
||||
//This is wrapped inside a setTimeout, because onClose is called before onChooseItem
|
||||
this.onClose = (): number =>
|
||||
window.setTimeout(() => reject("No Name selected"), 0);
|
||||
window.setTimeout(() => reject(new Error("No Name selected")), 0);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -58,14 +56,14 @@ export default class ChooseCustomNameModal extends SuggestModal<string> {
|
|||
}
|
||||
|
||||
// This isn't needed, since we just want a text field without options
|
||||
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-empty-function
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function -- intentionally a no-op, only a text field is needed
|
||||
public renderSuggestion(value: string, el: HTMLElement): void {}
|
||||
|
||||
// This will be overriden anyway, but typescript complains if it's not declared
|
||||
/* eslint-disable no-unused-vars, @typescript-eslint/no-empty-function */
|
||||
/* eslint-disable @typescript-eslint/no-empty-function -- overridden by awaitSelection, declared only to satisfy the type */
|
||||
public onChooseSuggestion(
|
||||
item: string,
|
||||
evt: MouseEvent | KeyboardEvent
|
||||
): void {}
|
||||
/* eslint-enable no-unused-vars, @typescript-eslint/no-empty-function */
|
||||
/* eslint-enable @typescript-eslint/no-empty-function -- overridden by awaitSelection, declared only to satisfy the type */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export default class ChooseIconModal extends FuzzySuggestModal<string> {
|
|||
this.onChooseItem = (item): void => resolve(item);
|
||||
//This is wrapped inside a setTimeout, because onClose is called before onChooseItem
|
||||
this.onClose = (): number =>
|
||||
window.setTimeout(() => reject("No Icon selected"), 0);
|
||||
window.setTimeout(() => reject(new Error("No Icon selected")), 0);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -62,6 +62,6 @@ export default class ChooseIconModal extends FuzzySuggestModal<string> {
|
|||
}
|
||||
|
||||
// This will be overriden anyway, but typescript complains if it's not declared
|
||||
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-empty-function
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function -- overridden by awaitSelection, declared only to satisfy the type
|
||||
public onChooseItem(_: string, __: MouseEvent | KeyboardEvent): void {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ export default function About({
|
|||
<button
|
||||
className="mod-cta"
|
||||
onClick={(e): void => {
|
||||
showConfetti(e);
|
||||
setTimeout(
|
||||
void showConfetti(e);
|
||||
window.setTimeout(
|
||||
() =>
|
||||
location.replace("https://forms.gle/hPjn61G9bqqFb3256"),
|
||||
Math.random() * 800 + 500
|
||||
|
|
@ -30,8 +30,8 @@ export default function About({
|
|||
<button
|
||||
className="mod-cta"
|
||||
onClick={(e): void => {
|
||||
showConfetti(e);
|
||||
setTimeout(
|
||||
void showConfetti(e);
|
||||
window.setTimeout(
|
||||
() => location.replace("https://ko-fi.com/phibr0"),
|
||||
Math.random() * 800 + 500
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,34 +1,82 @@
|
|||
import { Notice, Platform, setIcon, Setting } from "obsidian";
|
||||
import {
|
||||
ExtraButtonComponent,
|
||||
Notice,
|
||||
Platform,
|
||||
setIcon,
|
||||
Setting,
|
||||
SliderComponent,
|
||||
} from "obsidian";
|
||||
import { Fragment, h } from "preact";
|
||||
import { useEffect, useRef } from "preact/hooks";
|
||||
import { DEFAULT_SETTINGS } from "src/constants";
|
||||
import t from "src/l10n";
|
||||
import CommanderPlugin from "src/main";
|
||||
import { injectIcons, ObsidianIcon, updateStyles } from "src/util";
|
||||
import ChooseIconModal from "../chooseIconModal";
|
||||
|
||||
function addResettableSlider(
|
||||
containerEl: HTMLElement,
|
||||
opts: {
|
||||
name: string;
|
||||
desc: string;
|
||||
min: number;
|
||||
max: number;
|
||||
step: number;
|
||||
value: number;
|
||||
defaultValue: number;
|
||||
onChange: (value: number) => Promise<void>;
|
||||
}
|
||||
): void {
|
||||
let resetBtn: ExtraButtonComponent;
|
||||
let slider: SliderComponent;
|
||||
new Setting(containerEl)
|
||||
.setName(opts.name)
|
||||
.setDesc(opts.desc)
|
||||
.addSlider((cb) => {
|
||||
slider = cb;
|
||||
cb.setLimits(opts.min, opts.max, opts.step)
|
||||
.setValue(opts.value)
|
||||
.setDynamicTooltip()
|
||||
.onChange(async (value) => {
|
||||
await opts.onChange(value);
|
||||
resetBtn.setDisabled(value === opts.defaultValue);
|
||||
});
|
||||
})
|
||||
.addExtraButton((bt) => {
|
||||
resetBtn = bt;
|
||||
bt.setIcon("reset")
|
||||
.setTooltip(t("Restore default"))
|
||||
.setDisabled(opts.value === opts.defaultValue)
|
||||
.onClick(async () => {
|
||||
slider.setValue(opts.defaultValue);
|
||||
await opts.onChange(opts.defaultValue);
|
||||
resetBtn.setDisabled(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function render(containerEl: HTMLElement, plugin: CommanderPlugin): void {
|
||||
containerEl.empty();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Toolbar Row Count")
|
||||
.setDesc(
|
||||
"Set how many Rows the Mobile Toolbar should have. Set this to 0 to remove the Toolbar."
|
||||
)
|
||||
.addSlider((cb) =>
|
||||
cb
|
||||
.setLimits(0, 5, 1)
|
||||
.setValue(plugin.settings.advancedToolbar.rowCount)
|
||||
.setDynamicTooltip()
|
||||
.onChange(async (value) => {
|
||||
plugin.settings.advancedToolbar.rowCount = value;
|
||||
await plugin.saveSettings();
|
||||
updateStyles(plugin.settings.advancedToolbar);
|
||||
})
|
||||
);
|
||||
addResettableSlider(containerEl, {
|
||||
name: "Toolbar row count",
|
||||
desc: "Set how many rows the mobile toolbar should have. Set this to 0 to remove the toolbar.",
|
||||
min: 0,
|
||||
max: 5,
|
||||
step: 1,
|
||||
value: plugin.settings.advancedToolbar.rowCount,
|
||||
defaultValue: DEFAULT_SETTINGS.advancedToolbar.rowCount,
|
||||
onChange: async (value) => {
|
||||
plugin.settings.advancedToolbar.rowCount = value;
|
||||
await plugin.saveSettings();
|
||||
updateStyles(plugin.settings.advancedToolbar);
|
||||
},
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Column Layout")
|
||||
.setName("Column layout")
|
||||
.setDesc(
|
||||
"Use a column based layout instead of the default row. This makes it easier to arrange the Commands."
|
||||
"Use a column based layout instead of the default row. This makes it easier to arrange the commands."
|
||||
)
|
||||
.addToggle((cb) =>
|
||||
cb
|
||||
|
|
@ -51,26 +99,24 @@ function render(containerEl: HTMLElement, plugin: CommanderPlugin): void {
|
|||
// })
|
||||
// })
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Bottom Offset")
|
||||
.setDesc(
|
||||
"Offset the Toolbar from the Bottom of the Screen. This is useful if the toolbar is partially obscured by other UI Elements."
|
||||
)
|
||||
.addSlider((cb) =>
|
||||
cb
|
||||
.setLimits(0, 32, 1)
|
||||
.setValue(plugin.settings.advancedToolbar.heightOffset)
|
||||
.setDynamicTooltip()
|
||||
.onChange(async (value) => {
|
||||
plugin.settings.advancedToolbar.heightOffset = value;
|
||||
await plugin.saveSettings();
|
||||
updateStyles(plugin.settings.advancedToolbar);
|
||||
})
|
||||
);
|
||||
addResettableSlider(containerEl, {
|
||||
name: "Bottom offset",
|
||||
desc: "Offset the toolbar from the bottom of the screen. This is useful if the toolbar is partially obscured by other UI elements.",
|
||||
min: 0,
|
||||
max: 32,
|
||||
step: 1,
|
||||
value: plugin.settings.advancedToolbar.heightOffset,
|
||||
defaultValue: DEFAULT_SETTINGS.advancedToolbar.heightOffset,
|
||||
onChange: async (value) => {
|
||||
plugin.settings.advancedToolbar.heightOffset = value;
|
||||
await plugin.saveSettings();
|
||||
updateStyles(plugin.settings.advancedToolbar);
|
||||
},
|
||||
});
|
||||
|
||||
if (Platform.isMobile) {
|
||||
const description = document.createDocumentFragment();
|
||||
description.appendChild(createEl("h3", { text: "Custom Icons" }));
|
||||
const description = createFragment();
|
||||
description.appendChild(createEl("h3", { text: "Custom icons" }));
|
||||
containerEl.appendChild(description);
|
||||
|
||||
plugin.getCommandsWithoutIcons().forEach((command) => {
|
||||
|
|
@ -90,7 +136,7 @@ function render(containerEl: HTMLElement, plugin: CommanderPlugin): void {
|
|||
)?.iconID;
|
||||
currentIcon
|
||||
? setIcon(iconDiv, currentIcon)
|
||||
: bt.setButtonText("No Icon");
|
||||
: bt.setButtonText("No icon");
|
||||
}
|
||||
bt.onClick(async () => {
|
||||
const icon = await new ChooseIconModal(
|
||||
|
|
@ -115,7 +161,7 @@ function render(containerEl: HTMLElement, plugin: CommanderPlugin): void {
|
|||
})
|
||||
.addExtraButton((bt) => {
|
||||
bt.setIcon("reset")
|
||||
.setTooltip("Reset to default - Requires a restart")
|
||||
.setTooltip("Reset to default - requires a restart")
|
||||
.onClick(async () => {
|
||||
plugin.settings.advancedToolbar.mappedIcons =
|
||||
plugin.settings.advancedToolbar.mappedIcons.filter(
|
||||
|
|
@ -126,7 +172,7 @@ function render(containerEl: HTMLElement, plugin: CommanderPlugin): void {
|
|||
await plugin.saveSettings();
|
||||
render(containerEl, plugin);
|
||||
new Notice(
|
||||
"If the default Icon doesn't appear, you might have to restart Obsidian."
|
||||
"If the default icon doesn't appear, you might have to restart Obsidian."
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -134,22 +180,22 @@ function render(containerEl: HTMLElement, plugin: CommanderPlugin): void {
|
|||
}
|
||||
|
||||
const advancedEl = containerEl.appendChild(
|
||||
createEl("div", {
|
||||
createDiv({
|
||||
cls: "cmdr-sep-con",
|
||||
attr: { style: "margin-top: 64px" },
|
||||
})
|
||||
);
|
||||
advancedEl.appendChild(
|
||||
createEl("div", {
|
||||
text: "Advanced Settings",
|
||||
createDiv({
|
||||
text: "Advanced settings",
|
||||
attr: { style: "margin-bottom: 8px; font-weight: bold" },
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(advancedEl)
|
||||
.setName("Button Height")
|
||||
.setName("Button height")
|
||||
.setDesc(
|
||||
"Change the Height of each Button inside the Mobile Toolbar (in px)."
|
||||
"Change the height of each button inside the mobile toolbar (in px)."
|
||||
)
|
||||
.addText((cb) =>
|
||||
cb
|
||||
|
|
@ -170,9 +216,9 @@ function render(containerEl: HTMLElement, plugin: CommanderPlugin): void {
|
|||
})
|
||||
);
|
||||
new Setting(advancedEl)
|
||||
.setName("Button Width")
|
||||
.setName("Button width")
|
||||
.setDesc(
|
||||
"Change the Width of each Button inside the Mobile Toolbar (in px)."
|
||||
"Change the width of each button inside the mobile toolbar (in px)."
|
||||
)
|
||||
.addText((cb) =>
|
||||
cb
|
||||
|
|
@ -192,22 +238,20 @@ function render(containerEl: HTMLElement, plugin: CommanderPlugin): void {
|
|||
}
|
||||
})
|
||||
);
|
||||
new Setting(advancedEl)
|
||||
.setName("Toolbar Extra Spacing")
|
||||
.setDesc(
|
||||
"Some Themes need extra spacing in the toolbar. If your Toolbar doesn't wrap properly, try increasing this value."
|
||||
)
|
||||
.addSlider((cb) =>
|
||||
cb
|
||||
.setLimits(0, 64, 1)
|
||||
.setValue(plugin.settings.advancedToolbar.spacing)
|
||||
.setDynamicTooltip()
|
||||
.onChange(async (value) => {
|
||||
plugin.settings.advancedToolbar.spacing = value;
|
||||
await plugin.saveSettings();
|
||||
updateStyles(plugin.settings.advancedToolbar);
|
||||
})
|
||||
);
|
||||
addResettableSlider(advancedEl, {
|
||||
name: "Toolbar extra spacing",
|
||||
desc: "Some themes need extra spacing in the toolbar. If your toolbar doesn't wrap properly, try increasing this value.",
|
||||
min: 0,
|
||||
max: 64,
|
||||
step: 1,
|
||||
value: plugin.settings.advancedToolbar.spacing,
|
||||
defaultValue: DEFAULT_SETTINGS.advancedToolbar.spacing,
|
||||
onChange: async (value) => {
|
||||
plugin.settings.advancedToolbar.spacing = value;
|
||||
await plugin.saveSettings();
|
||||
updateStyles(plugin.settings.advancedToolbar);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export default function AdvancedToolbarSettings({
|
||||
|
|
@ -221,7 +265,9 @@ export default function AdvancedToolbarSettings({
|
|||
if (ref.current) {
|
||||
render(ref.current, plugin);
|
||||
}
|
||||
return () => ref.current && ref.current.empty();
|
||||
return (): void => {
|
||||
ref.current && ref.current.empty();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
|
@ -251,7 +297,11 @@ export default function AdvancedToolbarSettings({
|
|||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div ref={ref} style={{ paddingBottom: "128px" }} />
|
||||
<div
|
||||
ref={ref}
|
||||
className="cmdr-advanced-toolbar-settings"
|
||||
style={{ paddingBottom: "128px" }}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import { Ref, useEffect, useRef, useState } from "preact/hooks";
|
|||
|
||||
interface Props {
|
||||
value: string;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
handleChange: (e: h.JSX.TargetedKeyboardEvent<HTMLInputElement>) => void;
|
||||
ariaLabel: string;
|
||||
}
|
||||
|
|
@ -46,8 +45,7 @@ export default function ChangeableText({
|
|||
) : (
|
||||
<span
|
||||
onDblClick={({ target }): void => {
|
||||
/* @ts-ignore */
|
||||
setWidth(target?.offsetWidth);
|
||||
setWidth((target as HTMLElement | null)?.offsetWidth ?? 0);
|
||||
setShowInput(true);
|
||||
}}
|
||||
aria-label={ariaLabel}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import { useEffect, useRef } from "preact/hooks";
|
|||
|
||||
interface ColorPickerProps {
|
||||
initialColor: string;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
onChange: (_color: string) => void;
|
||||
}
|
||||
|
||||
|
|
@ -20,7 +19,9 @@ export const ColorPicker = ({
|
|||
.onChange(onChange);
|
||||
}
|
||||
|
||||
return () => ref.current?.empty?.();
|
||||
return (): void => {
|
||||
ref.current?.empty?.();
|
||||
};
|
||||
}, [onChange, initialColor]);
|
||||
|
||||
return <div ref={ref} className="cmdr-flex cmdr-items-center" />;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import { SliderComponent } from "./settingComponent";
|
|||
interface MacroBuilderProps {
|
||||
plugin: CommanderPlugin;
|
||||
macro: Macro;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
onSave: (macro: Macro) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
|
@ -24,7 +23,7 @@ export default function ({
|
|||
const [icon, setIcon] = useState(macro.icon || "star");
|
||||
const [startup, setStartup] = useState(macro.startup || false);
|
||||
const [macroCommands, setMacroCommands] = useState<MacroItem[]>(
|
||||
JSON.parse(JSON.stringify(macro.macro)) || []
|
||||
(JSON.parse(JSON.stringify(macro.macro)) as MacroItem[]) || []
|
||||
);
|
||||
|
||||
const handleAddCommand = async (): Promise<void> => {
|
||||
|
|
@ -238,8 +237,9 @@ export default function ({
|
|||
id="checkbox"
|
||||
checked={startup}
|
||||
onChange={({ target }): void => {
|
||||
//@ts-expect-error
|
||||
setStartup(target?.checked ?? false);
|
||||
setStartup(
|
||||
(target as HTMLInputElement | null)?.checked ?? false
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="checkbox">Auto-Run on Startup</label>
|
||||
|
|
|
|||
|
|
@ -7,14 +7,11 @@ import MacroBuilderComponent from "./MacroBuilder";
|
|||
export default class MacroBuilderModal extends Modal {
|
||||
private plugin: CommanderPlugin;
|
||||
private macro: Macro;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
private onSave: (_macro: Macro) => void;
|
||||
|
||||
public constructor(
|
||||
plugin: CommanderPlugin,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
macro: Macro,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
onSave: (_macro: Macro) => void
|
||||
) {
|
||||
super(plugin.app);
|
||||
|
|
@ -24,13 +21,13 @@ export default class MacroBuilderModal extends Modal {
|
|||
}
|
||||
|
||||
public onOpen(): void {
|
||||
this.titleEl.setText("Macro Builder");
|
||||
this.titleEl.setText("Macro builder");
|
||||
render(
|
||||
h(MacroBuilderComponent, {
|
||||
plugin: this.plugin,
|
||||
macro: this.macro,
|
||||
onSave: this.onSave,
|
||||
onCancel: this.close.bind(this),
|
||||
onCancel: (): void => this.close(),
|
||||
}),
|
||||
this.contentEl
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { Platform } from "obsidian";
|
||||
import { Fragment, h } from "preact";
|
||||
import { useState } from "preact/hooks";
|
||||
import t from "src/l10n";
|
||||
import CommanderPlugin from "src/main";
|
||||
import { Macro } from "src/types";
|
||||
|
|
@ -16,6 +17,9 @@ export default function MacroViewer({
|
|||
plugin,
|
||||
macros,
|
||||
}: MacroBuilderProps): h.JSX.Element {
|
||||
const [, setUpdateTick] = useState(0);
|
||||
const forceUpdate = (): void => setUpdateTick((tick) => tick + 1);
|
||||
|
||||
const handleBuilder = (macro: Macro, idx?: number): void => {
|
||||
const onClose = (updatedMacro: Macro): void => {
|
||||
macros.splice(
|
||||
|
|
@ -24,8 +28,8 @@ export default function MacroViewer({
|
|||
updatedMacro
|
||||
);
|
||||
|
||||
plugin.saveSettings();
|
||||
this.forceUpdate();
|
||||
void plugin.saveSettings();
|
||||
forceUpdate();
|
||||
updateMacroCommands(plugin);
|
||||
modal.close();
|
||||
};
|
||||
|
|
@ -35,8 +39,8 @@ export default function MacroViewer({
|
|||
|
||||
const handleDelete = (idx: number): void => {
|
||||
macros.splice(idx, 1);
|
||||
plugin.saveSettings();
|
||||
this.forceUpdate();
|
||||
void plugin.saveSettings();
|
||||
forceUpdate();
|
||||
updateMacroCommands(plugin);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -15,11 +15,8 @@ interface CommandViewerProps {
|
|||
handleUp: () => void;
|
||||
handleDown: () => void;
|
||||
handleNewIcon: () => void;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
handleRename: (_name: string) => void;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
handleModeChange: (_mode?: string) => void;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
handleColorChange: (_color?: string) => void;
|
||||
sortable?: boolean;
|
||||
}
|
||||
|
|
@ -100,12 +97,11 @@ export default function CommandComponent({
|
|||
);
|
||||
}
|
||||
const owningPluginID = cmd.id.split(":").first();
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const owningPlugin = plugin.app.plugins.manifests[owningPluginID!];
|
||||
const isInternal = !owningPlugin;
|
||||
const isChecked =
|
||||
cmd.hasOwnProperty("checkCallback") ||
|
||||
cmd.hasOwnProperty("editorCheckCallback");
|
||||
(Object.prototype.hasOwnProperty.call(cmd, "checkCallback") as boolean) ||
|
||||
(Object.prototype.hasOwnProperty.call(cmd, "editorCheckCallback") as boolean);
|
||||
|
||||
const modeIcon = getModeIcon(pair.mode);
|
||||
const modeName = pair.mode.match(/desktop|mobile|any/)
|
||||
|
|
@ -128,8 +124,9 @@ export default function CommandComponent({
|
|||
<ChangeableText
|
||||
ariaLabel={t("Double click to rename")}
|
||||
handleChange={({ target }): void => {
|
||||
/* @ts-ignore */
|
||||
handleRename(target?.value);
|
||||
handleRename(
|
||||
(target as HTMLInputElement | null)?.value ?? ""
|
||||
);
|
||||
}}
|
||||
value={pair.name}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { createContext, Fragment, h } from "preact";
|
||||
import { useState } from "preact/hooks";
|
||||
import CommanderPlugin from "src/main";
|
||||
import CommandComponent from "./commandComponent";
|
||||
import CommandManagerBase from "src/manager/commands/commandManager";
|
||||
|
|
@ -10,7 +11,6 @@ import t from "src/l10n";
|
|||
import { Platform } from "obsidian";
|
||||
import Logo from "./Logo";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
export const ManagerContext = createContext<CommandManagerBase>(null!);
|
||||
|
||||
interface CommandViewerProps {
|
||||
|
|
@ -25,6 +25,9 @@ export default function CommandViewer({
|
|||
children,
|
||||
sortable = true,
|
||||
}: CommandViewerProps): h.JSX.Element {
|
||||
const [, setUpdateTick] = useState(0);
|
||||
const forceUpdate = (): void => setUpdateTick((tick) => tick + 1);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<ManagerContext.Provider value={manager}>
|
||||
|
|
@ -48,7 +51,7 @@ export default function CommandViewer({
|
|||
).didChooseRemove())
|
||||
) {
|
||||
await manager.removeCommand(cmd);
|
||||
this.forceUpdate();
|
||||
forceUpdate();
|
||||
}
|
||||
}}
|
||||
handleUp={(): void => {
|
||||
|
|
@ -57,8 +60,8 @@ export default function CommandViewer({
|
|||
idx,
|
||||
idx - 1
|
||||
);
|
||||
manager.reorder();
|
||||
this.forceUpdate();
|
||||
void manager.reorder();
|
||||
forceUpdate();
|
||||
}}
|
||||
handleDown={(): void => {
|
||||
arrayMoveMutable(
|
||||
|
|
@ -66,16 +69,16 @@ export default function CommandViewer({
|
|||
idx,
|
||||
idx + 1
|
||||
);
|
||||
manager.reorder();
|
||||
this.forceUpdate();
|
||||
void manager.reorder();
|
||||
forceUpdate();
|
||||
}}
|
||||
handleRename={async (
|
||||
name
|
||||
): Promise<void> => {
|
||||
cmd.name = name;
|
||||
await plugin.saveSettings();
|
||||
manager.reorder();
|
||||
this.forceUpdate();
|
||||
await manager.reorder();
|
||||
forceUpdate();
|
||||
}}
|
||||
handleNewIcon={async (): Promise<void> => {
|
||||
const newIcon =
|
||||
|
|
@ -85,8 +88,8 @@ export default function CommandViewer({
|
|||
if (newIcon && newIcon !== cmd.icon) {
|
||||
cmd.icon = newIcon;
|
||||
await plugin.saveSettings();
|
||||
manager.reorder();
|
||||
this.forceUpdate();
|
||||
await manager.reorder();
|
||||
forceUpdate();
|
||||
}
|
||||
dispatchEvent(
|
||||
new Event("cmdr-icon-changed")
|
||||
|
|
@ -110,15 +113,16 @@ export default function CommandViewer({
|
|||
cmd.mode =
|
||||
mode || modes[currentIdx + 1];
|
||||
await plugin.saveSettings();
|
||||
manager.reorder();
|
||||
this.forceUpdate();
|
||||
await manager.reorder();
|
||||
forceUpdate();
|
||||
}}
|
||||
handleColorChange={async (
|
||||
color?: string
|
||||
): Promise<void> => {
|
||||
cmd.color = color;
|
||||
await plugin.saveSettings();
|
||||
manager.reorder();
|
||||
await manager.reorder();
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
@ -147,8 +151,8 @@ export default function CommandViewer({
|
|||
onClick={async (): Promise<void> => {
|
||||
const pair = await chooseNewCommand(plugin);
|
||||
await manager.addCommand(pair);
|
||||
manager.reorder();
|
||||
this.forceUpdate();
|
||||
await manager.reorder();
|
||||
forceUpdate();
|
||||
}}
|
||||
>
|
||||
{t("Add command")}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export function confirmDeleteComponent({
|
|||
className="mod-warning"
|
||||
onClick={async (): Promise<void> => {
|
||||
modal.plugin.settings.confirmDeletion = false;
|
||||
modal.plugin.saveSettings();
|
||||
await modal.plugin.saveSettings();
|
||||
|
||||
modal.remove = true;
|
||||
modal.close();
|
||||
|
|
|
|||
|
|
@ -18,8 +18,7 @@ export function LeftRibbonHider({
|
|||
const hiddenCommands = plugin.settings.hide.leftRibbon;
|
||||
useEffect(() => {
|
||||
setRibbonCommands(
|
||||
// @ts-expect-error
|
||||
app.workspace.leftRibbon.items.map((item) => ({
|
||||
plugin.app.workspace.leftRibbon.items.map((item) => ({
|
||||
name: item.title,
|
||||
icon: item.icon,
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { h } from "preact";
|
||||
import { useEffect } from "preact/hooks";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import t from "src/l10n";
|
||||
import { ObsidianIcon } from "src/util";
|
||||
import MobileModifyModal from "../mobileModifyModal";
|
||||
|
|
@ -7,18 +7,20 @@ import { ColorPicker } from "./ColorPicker";
|
|||
import CommanderPlugin from "src/main";
|
||||
|
||||
export default function MobileModifyComponent({
|
||||
plugin,
|
||||
plugin,
|
||||
modal: controller,
|
||||
}: {
|
||||
plugin: CommanderPlugin;
|
||||
modal: MobileModifyModal;
|
||||
}): h.JSX.Element {
|
||||
const [, setUpdateTick] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const update = (): void => {
|
||||
this.forceUpdate();
|
||||
setUpdateTick((tick) => tick + 1);
|
||||
};
|
||||
addEventListener("cmdr-icon-changed", update);
|
||||
return () => removeEventListener("cmdr-icon-changed", update);
|
||||
return (): void => removeEventListener("cmdr-icon-changed", update);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ function BaseComponent({
|
|||
interface SettingProps<T> {
|
||||
name: string;
|
||||
description: string;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
changeHandler: (value: T) => void;
|
||||
value: T;
|
||||
min?: number;
|
||||
|
|
@ -91,7 +90,13 @@ export function EyeToggleComponent({
|
|||
);
|
||||
}
|
||||
|
||||
export function SliderComponent(props: SettingProps<number>): h.JSX.Element {
|
||||
interface SliderProps extends SettingProps<number> {
|
||||
defaultValue?: number;
|
||||
}
|
||||
export function SliderComponent({
|
||||
defaultValue,
|
||||
...props
|
||||
}: SliderProps): h.JSX.Element {
|
||||
const [val, setVal] = useState(props.value);
|
||||
|
||||
return (
|
||||
|
|
@ -100,7 +105,26 @@ export function SliderComponent(props: SettingProps<number>): h.JSX.Element {
|
|||
name={props.name}
|
||||
className="cmdr-slider"
|
||||
>
|
||||
<div>
|
||||
<div class="cmdr-flex cmdr-items-center">
|
||||
{defaultValue !== undefined && (
|
||||
<ObsidianIcon
|
||||
aria-label={t("Restore default")}
|
||||
icon="reset"
|
||||
size={16}
|
||||
className={`clickable-icon${
|
||||
val === defaultValue ? " is-disabled" : ""
|
||||
}`}
|
||||
aria-disabled={val === defaultValue}
|
||||
onClick={
|
||||
val === defaultValue
|
||||
? undefined
|
||||
: (): void => {
|
||||
setVal(defaultValue);
|
||||
props.changeHandler(defaultValue);
|
||||
}
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<ChangeableText
|
||||
ariaLabel={t("Double click to enter custom value")}
|
||||
value={val.toString()}
|
||||
|
|
@ -121,12 +145,10 @@ export function SliderComponent(props: SettingProps<number>): h.JSX.Element {
|
|||
step={props.step ?? "1"}
|
||||
value={val}
|
||||
onPointerMove={({ target }): void => {
|
||||
{/*@ts-expect-error*/}
|
||||
if (val !== target.value) {
|
||||
{/*@ts-expect-error*/}
|
||||
setVal(target.value);
|
||||
{/*@ts-expect-error*/}
|
||||
props.changeHandler(target.value);
|
||||
const value = Number((target as HTMLInputElement).value);
|
||||
if (!isNaN(value) && val !== value) {
|
||||
setVal(value);
|
||||
props.changeHandler(value);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Platform } from "obsidian";
|
||||
import { Fragment, h } from "preact";
|
||||
import { useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
import { DEFAULT_SETTINGS } from "src/constants";
|
||||
import t from "src/l10n";
|
||||
import { Tab } from "src/types";
|
||||
import { ObsidianIcon, updateSpacing } from "src/util";
|
||||
|
|
@ -37,18 +38,16 @@ export default function settingTabComponent({
|
|||
|
||||
useEffect(() => {
|
||||
addEventListener("keydown", tabToNextTab);
|
||||
return () => removeEventListener("keydown", tabToNextTab);
|
||||
return (): void => removeEventListener("keydown", tabToNextTab);
|
||||
}, [activeTab]);
|
||||
|
||||
//This is used to remove the initial onclick event listener.
|
||||
if (Platform.isMobile) {
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const old_element = document.querySelector(
|
||||
".modal-setting-back-button"
|
||||
)!;
|
||||
const new_element = old_element.cloneNode(true);
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
old_element.parentNode!.replaceChild(new_element, old_element);
|
||||
setOpen(true);
|
||||
}, []);
|
||||
|
|
@ -61,11 +60,9 @@ export default function settingTabComponent({
|
|||
if (!el) return;
|
||||
|
||||
if (!open) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
el.parentElement!.lastChild!.textContent = tabs[activeTab].name;
|
||||
el.onclick = (): void => setOpen(true);
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
el.parentElement!.lastChild!.textContent = "Commander";
|
||||
el.onclick = (): void => plugin.app.setting.closeActiveTab();
|
||||
}
|
||||
|
|
@ -96,12 +93,13 @@ export default function settingTabComponent({
|
|||
}
|
||||
changeHandler={async (value): Promise<void> => {
|
||||
plugin.settings.showAddCommand = !value;
|
||||
plugin.manager.pageHeader.reorder();
|
||||
await plugin.manager.pageHeader.reorder();
|
||||
await plugin.saveSettings();
|
||||
}}
|
||||
/>
|
||||
<SliderComponent
|
||||
value={plugin.settings.spacing}
|
||||
defaultValue={DEFAULT_SETTINGS.spacing}
|
||||
name={t(
|
||||
"Choose custom spacing for Command Buttons"
|
||||
)}
|
||||
|
|
@ -172,7 +170,7 @@ export default function settingTabComponent({
|
|||
<button
|
||||
onClick={(): void => {
|
||||
plugin.app.setting.openTabById("appearance");
|
||||
setTimeout(() => {
|
||||
window.setTimeout(() => {
|
||||
plugin.app.setting.activeTab.containerEl.scroll(
|
||||
{
|
||||
behavior: "smooth",
|
||||
|
|
@ -180,14 +178,14 @@ export default function settingTabComponent({
|
|||
}
|
||||
);
|
||||
|
||||
plugin.app.setting.activeTab.containerEl
|
||||
.querySelectorAll(
|
||||
".setting-item-heading"
|
||||
)[1]
|
||||
// @ts-ignore
|
||||
.nextSibling?.nextSibling?.nextSibling?.addClass?.(
|
||||
"cmdr-cta"
|
||||
);
|
||||
(
|
||||
plugin.app.setting.activeTab.containerEl
|
||||
.querySelectorAll(
|
||||
".setting-item-heading"
|
||||
)[1]
|
||||
.nextSibling?.nextSibling
|
||||
?.nextSibling as HTMLElement | null
|
||||
)?.addClass?.("cmdr-cta");
|
||||
}, 50);
|
||||
}}
|
||||
className="mod-cta"
|
||||
|
|
@ -323,9 +321,7 @@ export default function settingTabComponent({
|
|||
interface TabHeaderProps {
|
||||
tabs: Tab[];
|
||||
activeTab: number;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
setActiveTab: (idx: number) => void;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
setOpen: (open: boolean) => void;
|
||||
}
|
||||
export function TabHeader({
|
||||
|
|
@ -348,7 +344,7 @@ export function TabHeader({
|
|||
}
|
||||
|
||||
el.addEventListener("wheel", handleScroll);
|
||||
return () => el.removeEventListener("wheel", handleScroll);
|
||||
return (): void => el.removeEventListener("wheel", handleScroll);
|
||||
}, []);
|
||||
|
||||
useEffect(
|
||||
|
|
|
|||
|
|
@ -1,23 +1,20 @@
|
|||
import { Modal } from "obsidian";
|
||||
import { h, render, VNode } from "preact";
|
||||
import { h, render } from "preact";
|
||||
import t from "src/l10n";
|
||||
import CommanderPlugin from "src/main";
|
||||
import { confirmDeleteComponent } from "./components/confirmDeleteComponent";
|
||||
|
||||
export default class ConfirmDeleteModal extends Modal {
|
||||
private reactComponent: VNode;
|
||||
public remove: boolean;
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
public constructor(public plugin: CommanderPlugin) {
|
||||
super(plugin.app);
|
||||
}
|
||||
|
||||
public async onOpen(): Promise<void> {
|
||||
this.titleEl.innerText = t("Remove Command");
|
||||
this.containerEl.style.zIndex = "99";
|
||||
this.reactComponent = h(confirmDeleteComponent, { modal: this });
|
||||
render(this.reactComponent, this.contentEl);
|
||||
this.containerEl.addClass("cmdr-confirm-delete-modal");
|
||||
render(h(confirmDeleteComponent, { modal: this }), this.contentEl);
|
||||
}
|
||||
|
||||
public async didChooseRemove(): Promise<boolean> {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
/* eslint-disable no-unused-vars */
|
||||
import { CommandIconPair } from "src/types";
|
||||
import { Modal } from "obsidian";
|
||||
import { h, render, VNode } from "preact";
|
||||
import { h, render } from "preact";
|
||||
import MobileModifyComponent from "./components/mobileModifyComponent";
|
||||
import CommanderPlugin from "src/main";
|
||||
|
||||
export default class MobileModifyModal extends Modal {
|
||||
private reactComponent: VNode;
|
||||
public remove: boolean;
|
||||
|
||||
public constructor(
|
||||
|
|
@ -22,11 +20,13 @@ export default class MobileModifyModal extends Modal {
|
|||
|
||||
public async onOpen(): Promise<void> {
|
||||
this.titleEl.innerText = this.pair.name;
|
||||
this.reactComponent = h(MobileModifyComponent, {
|
||||
plugin: this.plugin,
|
||||
modal: this,
|
||||
});
|
||||
render(this.reactComponent, this.contentEl);
|
||||
render(
|
||||
h(MobileModifyComponent, {
|
||||
plugin: this.plugin,
|
||||
modal: this,
|
||||
}),
|
||||
this.contentEl
|
||||
);
|
||||
}
|
||||
|
||||
public onClose(): void {
|
||||
|
|
|
|||
20
src/util.tsx
20
src/util.tsx
|
|
@ -24,7 +24,7 @@ export async function chooseNewCommand(
|
|||
const command = await new AddCommandModal(plugin).awaitSelection();
|
||||
|
||||
let icon;
|
||||
if (!command.hasOwnProperty("icon")) {
|
||||
if (!(Object.prototype.hasOwnProperty.call(command, "icon") as boolean)) {
|
||||
icon = await new ChooseIconModal(plugin).awaitSelection();
|
||||
}
|
||||
|
||||
|
|
@ -36,7 +36,6 @@ export async function chooseNewCommand(
|
|||
return {
|
||||
id: command.id,
|
||||
//This cannot be undefined anymore
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
icon: icon ?? command.icon!,
|
||||
name: name || command.name,
|
||||
mode: "any",
|
||||
|
|
@ -59,7 +58,6 @@ export function ObsidianIcon({
|
|||
const iconEl = useRef<HTMLDivElement>(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
setIcon(iconEl.current!, icon);
|
||||
}, [icon, size]);
|
||||
|
||||
|
|
@ -99,17 +97,10 @@ export function updateHiderStylesheet(settings: CommanderSettings): void {
|
|||
}
|
||||
|
||||
export async function showConfetti({ target }: MouseEvent): Promise<void> {
|
||||
const myCanvas = activeDocument.createElement("canvas");
|
||||
const myCanvas = createEl("canvas", {
|
||||
cls: "cmdr-confetti-canvas",
|
||||
});
|
||||
activeDocument.body.appendChild(myCanvas);
|
||||
myCanvas.style.position = "fixed";
|
||||
myCanvas.style.width = "100vw";
|
||||
myCanvas.style.height = "100vh";
|
||||
myCanvas.style.top = "0px";
|
||||
myCanvas.style.left = "0px";
|
||||
//@ts-ignore
|
||||
myCanvas.style["pointer-events"] = "none";
|
||||
//@ts-ignore
|
||||
myCanvas.style["z-index"] = "100";
|
||||
|
||||
const myConfetti = confetti.create(myCanvas, {
|
||||
resize: true,
|
||||
|
|
@ -143,7 +134,6 @@ export function updateMacroCommands(plugin: CommanderPlugin): void {
|
|||
p.startsWith("cmdr:macro-")
|
||||
);
|
||||
for (const command of oldCommands) {
|
||||
//@ts-ignore
|
||||
plugin.app.commands.removeCommand(command);
|
||||
}
|
||||
|
||||
|
|
@ -154,7 +144,7 @@ export function updateMacroCommands(plugin: CommanderPlugin): void {
|
|||
name: macro.name,
|
||||
icon: macro.icon,
|
||||
callback: () => {
|
||||
plugin.executeMacro(Number(idx));
|
||||
void plugin.executeMacro(Number(idx));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
29
vitest.config.ts
Normal file
29
vitest.config.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { defineConfig } from "vitest/config";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
environment: "jsdom",
|
||||
globals: false,
|
||||
setupFiles: ["src/__tests__/setup.ts"],
|
||||
},
|
||||
resolve: {
|
||||
alias: [
|
||||
{
|
||||
find: "obsidian",
|
||||
replacement: path.resolve(__dirname, "src/__tests__/__mocks__/obsidian.ts"),
|
||||
},
|
||||
{
|
||||
find: /.*\/l10n$/,
|
||||
replacement: path.resolve(__dirname, "src/__tests__/__mocks__/l10n.ts"),
|
||||
},
|
||||
{
|
||||
find: "src",
|
||||
replacement: path.resolve(__dirname, "src"),
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
Loading…
Reference in a new issue