chore: migrate tooling to biome and pnpm

This commit is contained in:
memodack 2025-11-10 16:23:34 +02:00
parent cb6a171b75
commit 6f3070977d
36 changed files with 4886 additions and 8306 deletions

View file

@ -1,4 +1,4 @@
npm run prettier
npm run lint
npm run lint:circulars
npm run test
pnpm run format
pnpm run lint
pnpm run lint:circulars
pnpm run test

16
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,16 @@
{
// General editor settings
"editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true,
// Language-specific formatter overrides
"[javascript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[json]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
}
}

36
biome.json Normal file
View file

@ -0,0 +1,36 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.4/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"includes": ["**", "!!**/dist"]
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 80
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"javascript": {
"formatter": {
"quoteStyle": "double"
}
},
"assist": {
"enabled": true,
"actions": {
"source": {
"organizeImports": "on"
}
}
}
}

View file

@ -1,3 +1,3 @@
export default {
extends: ['@commitlint/config-conventional'],
extends: ["@commitlint/config-conventional"],
};

View file

@ -1,45 +1,26 @@
import { copyFile, readFile, rm, writeFile } from 'node:fs/promises';
import { existsSync, watchFile } from "node:fs";
import { copyFile, readFile, rm, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { exit } from "node:process";
import builtins from "builtin-modules";
import CleanCSS from "clean-css";
import { context } from "esbuild";
import CleanCSS from 'clean-css';
import builtins from 'builtin-modules';
import { context } from 'esbuild';
import { existsSync } from 'node:fs';
import { exit } from 'node:process';
import { join } from 'node:path';
import { watchFile } from 'fs';
const isDevelopment = process.env.NODE_ENV === "development";
const isProduction = process.env.NODE_ENV === "production";
const isDevelopment = process.env.NODE_ENV === 'development';
const isProduction = process.env.NODE_ENV === 'production';
const outdirPath = 'dist';
const stylesCssFilePath = 'src/styles.css';
const 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,
];
const outdirPath = "dist";
const stylesCssFilePath = "src/styles.css";
if (existsSync(outdirPath)) {
await rm(outdirPath, { recursive: true, force: true });
}
const stylesCopy = async () => {
const css = await readFile(stylesCssFilePath, 'utf8');
const css = await readFile(stylesCssFilePath, "utf8");
const minified = new CleanCSS().minify(css).styles;
await writeFile(join(outdirPath, 'styles.css'), minified);
await writeFile(join(outdirPath, "styles.css"), minified);
};
watchFile(stylesCssFilePath, { interval: 300 }, async () => {
@ -47,7 +28,7 @@ watchFile(stylesCssFilePath, { interval: 300 }, async () => {
});
const stylesCopyPlugin = () => ({
name: 'styles-copy-plugin',
name: "styles-copy-plugin",
setup(build) {
build.onEnd(async () => {
await stylesCopy();
@ -56,20 +37,37 @@ const stylesCopyPlugin = () => ({
});
const manifestCopyPlugin = () => ({
name: 'manifest-copy-plugin',
name: "manifest-copy-plugin",
setup(build) {
build.onEnd(async () => {
await copyFile('manifest.json', join(outdirPath, 'manifest.json'));
await copyFile("manifest.json", join(outdirPath, "manifest.json"));
});
},
});
const 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,
];
const ctx = await context({
entryPoints: ['src/main.ts'],
entryPoints: ["src/main.ts"],
outfile: `${outdirPath}/main.js`,
bundle: true,
minify: isProduction,
format: 'cjs',
format: "cjs",
external,
plugins: [stylesCopyPlugin(), manifestCopyPlugin()],
});

View file

@ -1,61 +0,0 @@
import eslint from '@eslint/js';
import globals from 'globals';
import pluginJest from 'eslint-plugin-jest';
import tseslint from 'typescript-eslint';
export default tseslint.config(
// Basic JS and TS rules
eslint.configs.recommended,
tseslint.configs.strictTypeChecked,
// Language settings for regular code (without jest!)
{
languageOptions: {
globals: globals.node,
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
},
// Strict TypeScript rules
{
rules: {
'@typescript-eslint/explicit-function-return-type': 'error',
},
},
// Separate block for tests
{
files: ['**/*.test.ts'],
plugins: { jest: pluginJest },
languageOptions: {
globals: pluginJest.environments.globals.globals,
},
rules: {
// Jest rules
'jest/no-disabled-tests': 'warn',
'jest/no-focused-tests': 'error',
'jest/no-identical-title': 'error',
'jest/prefer-to-have-length': 'warn',
'jest/valid-expect': 'error',
// Special handling of unbound methods
'@typescript-eslint/unbound-method': 'off',
'jest/unbound-method': 'error',
},
},
// Ignored files
{
ignores: [
'dist',
'eslint.config.mjs',
'commitlint.config.mjs',
'prettier.config.mjs',
'jest.config.mjs',
'esbuild.config.mjs',
],
},
);

View file

@ -1,4 +1,4 @@
export default {
preset: 'ts-jest',
testEnvironment: 'node',
preset: "ts-jest",
testEnvironment: "node",
};

7967
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -7,35 +7,33 @@
"scripts": {
"build": "cross-env NODE_ENV=production node ./esbuild.config.mjs",
"dev": "cross-env NODE_ENV=development node ./esbuild.config.mjs",
"lint": "eslint .",
"format": "biome format",
"format:write": "biome format --write",
"lint": "biome lint",
"lint:circulars": "madge --circular --extensions ts ./",
"lint:fix": "eslint . --fix",
"prepare": "husky",
"prettier": "prettier --check .",
"prettier:fix": "prettier --write .",
"lint:write": "biome lint --write",
"test": "jest --forceExit"
},
"dependencies": {
"pretty-bytes": "^6.1.1"
},
"devDependencies": {
"@biomejs/biome": "^2.3.4",
"@commitlint/cli": "^19.8.0",
"@commitlint/config-conventional": "^19.8.0",
"@eslint/js": "^9.23.0",
"@types/jest": "^29.5.14",
"builtin-modules": "^5.0.0",
"clean-css": "^5.3.3",
"cross-env": "^7.0.3",
"cross-env": "^10.1.0",
"esbuild": "^0.25.3",
"eslint": "^9.23.0",
"eslint-plugin-jest": "^28.11.0",
"husky": "^9.1.7",
"jest": "^29.7.0",
"madge": "^8.0.0",
"obsidian": "^1.8.7",
"prettier": "^3.5.3",
"ts-jest": "^29.3.2",
"typescript": "^5.8.2",
"typescript-eslint": "^8.28.0"
"typescript": "^5.8.2"
},
"engines": {
"pnpm": ">=10.17.0"
}
}

4564
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load diff

2
pnpm-workspace.yaml Normal file
View file

@ -0,0 +1,2 @@
onlyBuiltDependencies:
- esbuild

View file

@ -1,10 +0,0 @@
export default {
semi: true,
singleQuote: true,
trailingComma: 'all',
printWidth: 80,
tabWidth: 2,
useTabs: false,
bracketSpacing: true,
arrowParens: 'always',
};

View file

@ -1,27 +1,26 @@
import { Plugin, addIcon } from 'obsidian';
import { AdapterService } from './services/adapter.service';
import { AudioService } from './services/audio.service';
import { BlitzModalService } from './services/blitz-modal.service';
import { CacheService } from './services/cache.service';
import { ConductorService } from './services/conductor.service';
import { EditorService } from './services/editor.service';
import { ManifestService } from './services/manifest.service';
import { MppService } from './services/mpp.service';
import { PartsService } from './services/parts.service';
import { PathsService } from './services/paths.service';
import { PlayerService } from './services/player.service';
import { RibbonIconService } from './services/ribbon-icon.service';
import { SettingTabService } from './services/setting-tab.service';
import { TSettings } from './types';
import { TranslateCommandService } from './services/translate-command.service';
import { TranslationService } from './services/translation.service';
import { TtsService } from './services/tts.service';
import { VaultService } from './services/vault.service';
import { WorkspaceService } from './services/workspace.service';
import { blitzService } from './services/blitz.service';
import { progressBarService } from './services/progress-bar.service';
import { settingsService } from './services/settings.service';
import { addIcon, Plugin } from "obsidian";
import { AdapterService } from "./services/adapter.service";
import { AudioService } from "./services/audio.service";
import { blitzService } from "./services/blitz.service";
import { BlitzModalService } from "./services/blitz-modal.service";
import { CacheService } from "./services/cache.service";
import { ConductorService } from "./services/conductor.service";
import { EditorService } from "./services/editor.service";
import { ManifestService } from "./services/manifest.service";
import { MppService } from "./services/mpp.service";
import { PartsService } from "./services/parts.service";
import { PathsService } from "./services/paths.service";
import { PlayerService } from "./services/player.service";
import { progressBarService } from "./services/progress-bar.service";
import { RibbonIconService } from "./services/ribbon-icon.service";
import { SettingTabService } from "./services/setting-tab.service";
import { settingsService } from "./services/settings.service";
import { TranslateCommandService } from "./services/translate-command.service";
import { TranslationService } from "./services/translation.service";
import { TtsService } from "./services/tts.service";
import { VaultService } from "./services/vault.service";
import { WorkspaceService } from "./services/workspace.service";
import type { TSettings } from "./types";
export default class MemodackPlugin extends Plugin {
async loadSettings(): Promise<void> {

View file

@ -1,4 +1,9 @@
import { DataAdapter, DataWriteOptions, ListedFiles, Stat } from 'obsidian';
import type {
DataAdapter,
DataWriteOptions,
ListedFiles,
Stat,
} from "obsidian";
export interface IAdapterService {
exists(normalizedPath: string, sensitive?: boolean): Promise<boolean>;

View file

@ -1,7 +1,7 @@
import { ELanguage } from '../types';
import { ICacheService } from './cache.service';
import { IPlayerService } from './player.service';
import { ITtsService } from './tts.service';
import type { ELanguage } from "../types";
import type { ICacheService } from "./cache.service";
import type { IPlayerService } from "./player.service";
import type { ITtsService } from "./tts.service";
type TPlayArgs = { source: ELanguage; value: string }[];
@ -66,15 +66,15 @@ export class AudioService implements IAudioService {
const msgBuffer = new TextEncoder().encode(data);
// Compute the hash
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer);
// Convert the hash to a byte array
const hashArray = Array.from(new Uint8Array(hashBuffer));
// Convert the byte array to a hexadecimal string
const hashHex = hashArray
.map((b) => b.toString(16).padStart(2, '0'))
.join('');
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
return hashHex;
}

View file

@ -1,9 +1,9 @@
import { App, Modal } from 'obsidian';
import { type App, Modal } from "obsidian";
import { IBlitzService } from './blitz.service';
import { IConductorService } from './conductor.service';
import { IPart } from './parts.service';
import { IProgressBarService } from './progress-bar.service';
import type { IBlitzService } from "./blitz.service";
import type { IConductorService } from "./conductor.service";
import type { IPart } from "./parts.service";
import type { IProgressBarService } from "./progress-bar.service";
export interface IBlitzModalService {
setParts(parts: IPart[]): void;
@ -65,16 +65,16 @@ export class BlitzModalService extends Modal implements IBlitzModalService {
void this.conductorService.playValue(blitz.question);
let nextButtonEl: HTMLButtonElement | undefined = undefined;
let correctOptionEl: HTMLButtonElement | undefined = undefined;
let nextButtonEl: HTMLButtonElement | undefined;
let correctOptionEl: HTMLButtonElement | undefined;
const answersButtons: HTMLButtonElement[] = [];
const answersElement = contentEl.createEl('div');
answersElement.addClass('memodack___blitz__answers');
const answersElement = contentEl.createEl("div");
answersElement.addClass("memodack___blitz__answers");
blitz.answers.forEach((item, index) => {
const answerButtonElement = answersElement.createEl('button');
const answerButtonElement = answersElement.createEl("button");
if (blitz.correctAnswerId === index) {
correctOptionEl = answerButtonElement;
@ -83,17 +83,17 @@ export class BlitzModalService extends Modal implements IBlitzModalService {
answersButtons.push(answerButtonElement);
answerButtonElement.setText(item);
answerButtonElement.addEventListener('click', () => {
answerButtonElement.addEventListener("click", () => {
answersButtons.forEach((item) => {
item.disabled = true;
});
if (blitz.correctAnswerId === index) {
answerButtonElement.addClass('correct');
answerButtonElement.addClass("correct");
void this.conductorService.playTranslation(item);
} else {
answerButtonElement.addClass('wrong');
correctOptionEl?.addClass('correct');
answerButtonElement.addClass("wrong");
correctOptionEl?.addClass("correct");
this.blitzService.repeatBlitz(id);
}
@ -115,29 +115,29 @@ export class BlitzModalService extends Modal implements IBlitzModalService {
answersElement.appendChild(this.progressBarService.getElement());
this.progressBarService.setValue(this.blitzService.getProgress());
const blitzNext = contentEl.createEl('div');
blitzNext.addClass('memodack___blitz__next');
const blitzNext = contentEl.createEl("div");
blitzNext.addClass("memodack___blitz__next");
nextButtonEl = blitzNext.createEl('button');
nextButtonEl.setText('Next');
nextButtonEl = blitzNext.createEl("button");
nextButtonEl.setText("Next");
nextButtonEl.disabled = true;
nextButtonEl.addEventListener('click', () => {
nextButtonEl.addEventListener("click", () => {
this.createBlitz(id + 1);
});
}
private createQuestionElement(question: string): void {
const { contentEl } = this;
const questionH2Element = contentEl.createEl('h2');
const questionH2Element = contentEl.createEl("h2");
questionH2Element.setText(question);
questionH2Element.addClass('memodack___blitz__question');
questionH2Element.addClass("memodack___blitz__question");
}
private createTextElement(text: string): void {
const { contentEl } = this;
const questionH2Element = contentEl.createEl('div');
const questionH2Element = contentEl.createEl("div");
questionH2Element.setText(text);
questionH2Element.addClass('memodack___blitz__text');
questionH2Element.addClass("memodack___blitz__text");
}
}

View file

@ -1,6 +1,5 @@
import { ISettingsService, settingsService } from './settings.service';
import { IPart } from './parts.service';
import type { IPart } from "./parts.service";
import { type ISettingsService, settingsService } from "./settings.service";
export interface IBlitz {
correctAnswerId: number;
@ -55,9 +54,7 @@ export class BlitzService implements IBlitzService {
const shuffleAnswers = this.shuffle(answers);
const correctAnswerId = shuffleAnswers.findIndex(
(shuffleAnswerItem) => shuffleAnswerItem === q1,
);
const correctAnswerId = shuffleAnswers.indexOf(q1);
this.blitzMap.set(index, {
correctAnswerId,
@ -93,9 +90,7 @@ export class BlitzService implements IBlitzService {
const blitzTranslation = blitz.answers[blitz.correctAnswerId];
const correctAnswerId = shuffleAnswers.findIndex(
(shuffleAnswerItem) => shuffleAnswerItem === blitzTranslation,
);
const correctAnswerId = shuffleAnswers.indexOf(blitzTranslation);
this.blitzMap.set(this.blitzMap.size, {
correctAnswerId,
@ -132,7 +127,7 @@ export class BlitzService implements IBlitzService {
): number[] {
if (maxNumber < 1 || count < 1) {
throw new Error(
'The maxNumber must be greater than or equal to 1 and count must be greater than or equal to 1.',
"The maxNumber must be greater than or equal to 1 and count must be greater than or equal to 1.",
);
}
@ -145,7 +140,7 @@ export class BlitzService implements IBlitzService {
if (availableNumbers.size < count) {
throw new Error(
'Not enough unique numbers available to satisfy the count.',
"Not enough unique numbers available to satisfy the count.",
);
}
@ -167,7 +162,7 @@ export class BlitzService implements IBlitzService {
*/
private shuffle<T>(array: T[]): T[] {
if (!Array.isArray(array)) {
throw new Error('Input array cannot be null or undefined.');
throw new Error("Input array cannot be null or undefined.");
}
// Create a copy of the array to avoid modifying the original
@ -187,7 +182,7 @@ export class BlitzService implements IBlitzService {
/**
* Split the text to the parts and return randomly one of it.
*/
private getRandomSegment(text: string, divider: string = ';'): string {
private getRandomSegment(text: string, divider: string = ";"): string {
const parts = text.split(divider).map((item) => item.trim());
const index = Math.floor(Math.random() * parts.length);
return parts[index];

View file

@ -1,6 +1,6 @@
import { IAdapterService } from './adapter.service';
import { IPathsService } from './paths.service';
import { IVaultService } from './vault.service';
import type { IAdapterService } from "./adapter.service";
import type { IPathsService } from "./paths.service";
import type { IVaultService } from "./vault.service";
export interface ICacheService {
add(key: string, value: string): Promise<void>;
@ -37,7 +37,7 @@ export class CacheService implements ICacheService {
}
} catch (e) {
const errorMessage = `Failed to add cache for key '${key}'.`;
console.error(errorMessage, e instanceof Error ? e.message : '');
console.error(errorMessage, e instanceof Error ? e.message : "");
}
}
@ -52,7 +52,7 @@ export class CacheService implements ICacheService {
return await this.adapterService.read(`${cacheDirPath}/${key}`);
} catch (e) {
const errorMessage = `Failed to get a cache by '${key}' key.`;
console.error(errorMessage, e instanceof Error ? e.message : '');
console.error(errorMessage, e instanceof Error ? e.message : "");
return null;
}
@ -80,7 +80,7 @@ export class CacheService implements ICacheService {
return totalSize;
} catch (e) {
console.error(
`Failed to retrieve the cache directory size. ${e instanceof Error ? e.message : ''}`,
`Failed to retrieve the cache directory size. ${e instanceof Error ? e.message : ""}`,
);
return 0;
@ -98,7 +98,7 @@ export class CacheService implements ICacheService {
await this.adapterService.rmdir(cacheDirPath, true);
} catch (e) {
console.error(
`Failed to clear the cache. ${e instanceof Error ? e.message : ''}`,
`Failed to clear the cache. ${e instanceof Error ? e.message : ""}`,
);
}
}

View file

@ -1,6 +1,6 @@
import { EPlayVariant } from '../types';
import { IAudioService } from './audio.service';
import { ISettingsService } from './settings.service';
import { EPlayVariant } from "../types";
import type { IAudioService } from "./audio.service";
import type { ISettingsService } from "./settings.service";
export interface IConductorService {
playValue(value: string): Promise<void>;

View file

@ -1,4 +1,4 @@
import { Editor } from 'obsidian';
import type { Editor } from "obsidian";
export interface IEditorService {
getSelection(): string;

View file

@ -1,4 +1,4 @@
import { PluginManifest } from 'obsidian';
import type { PluginManifest } from "obsidian";
export interface IManifestService {
getId(): string;

View file

@ -1,4 +1,4 @@
import { IConductorService } from './conductor.service';
import type { IConductorService } from "./conductor.service";
export interface IMppService {
getPostProcessor(element: HTMLElement): void;
@ -15,10 +15,13 @@ export class MppService implements IMppService {
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);
const nodesToReplace = [];
let node;
let node: Node | null;
while ((node = walker.nextNode())) {
if (node.nodeValue && node.nodeValue.match(/\{.*?\|.*?\}/)) {
while (true) {
node = walker.nextNode() as Text | null;
if (!node) break;
if (node.nodeValue?.match(/\{.*?\|.*?\}/)) {
nodesToReplace.push(node);
}
}
@ -47,11 +50,11 @@ export class MppService implements IMppService {
const value = match[1];
const translation = match[2];
const span = createEl('span', {
cls: 'memodack___syntax',
const span = createEl("span", {
cls: "memodack___syntax",
text: value,
attr: {
'data-translation': match[2],
"data-translation": match[2],
},
});

View file

@ -1,5 +1,5 @@
import { IVaultService } from './vault.service';
import { IWorkspaceService } from './workspace.service';
import type { IVaultService } from "./vault.service";
import type { IWorkspaceService } from "./workspace.service";
export interface IPart {
value: string;
@ -41,7 +41,7 @@ export class PartsService implements IPartsService {
const parts: IPart[] = [];
const texts = content.split('\n');
const texts = content.split("\n");
// Generate parts array
matches.forEach((match) => {
@ -90,7 +90,7 @@ export class PartsService implements IPartsService {
if (selection && selection.rangeCount > 0 && selection.toString().length) {
const ranges = selection.getRangeAt(0);
const spans = document.querySelectorAll('.memodack___syntax');
const spans = document.querySelectorAll(".memodack___syntax");
// Iterate over all <span> elements
spans.forEach((span) => {
@ -99,8 +99,8 @@ export class PartsService implements IPartsService {
// Check if the selection intersects with the <span> element
if (ranges.intersectsNode(span)) {
const beforeText = span.previousSibling?.textContent?.trim() || '';
const afterText = span.nextSibling?.textContent?.trim() || '';
const beforeText = span.previousSibling?.textContent?.trim() || "";
const afterText = span.nextSibling?.textContent?.trim() || "";
const value = span.textContent;
@ -108,7 +108,7 @@ export class PartsService implements IPartsService {
return;
}
const translation = span.getAttribute('data-translation');
const translation = span.getAttribute("data-translation");
const text = `${beforeText} ${value} ${afterText}`.trim();

View file

@ -1,5 +1,5 @@
import { IManifestService } from './manifest.service';
import { IVaultService } from './vault.service';
import type { IManifestService } from "./manifest.service";
import type { IVaultService } from "./vault.service";
export interface IPathsService {
getCacheDirPath(): string;

View file

@ -1,4 +1,4 @@
import { ISettingsService } from './settings.service';
import type { ISettingsService } from "./settings.service";
export interface IPlayerService {
play(audioUrl: string): Promise<void>;
@ -14,7 +14,7 @@ export class PlayerService implements IPlayerService {
async play(audioUrl: string): Promise<void> {
if (!audioUrl) {
throw new Error('The audioUrl is required for audio playback.');
throw new Error("The audioUrl is required for audio playback.");
}
this.audio.volume = 0;
@ -28,7 +28,7 @@ export class PlayerService implements IPlayerService {
await new Promise<void>((resolve) => {
this.audio.addEventListener(
'ended',
"ended",
() => {
resolve();
},
@ -36,7 +36,7 @@ export class PlayerService implements IPlayerService {
);
});
} catch (e) {
const errorMessage = 'Audio playback error.';
const errorMessage = "Audio playback error.";
if (e instanceof Error) {
console.error(e.message || errorMessage);

View file

@ -10,8 +10,8 @@ export class ProgressBarService implements IProgressBarService {
private _progressElement: HTMLProgressElement | null = null;
create(contentEl: HTMLElement, max: number, value: number): void {
this._progressElement = contentEl.createEl('progress');
this._progressElement.addClass('memodack___blitz__progress');
this._progressElement = contentEl.createEl("progress");
this._progressElement.addClass("memodack___blitz__progress");
this.setMax(max);
this.setValue(value);
@ -35,7 +35,7 @@ export class ProgressBarService implements IProgressBarService {
private get progressElement(): HTMLProgressElement {
if (!this._progressElement) {
throw new Error('The progress element has not been created.');
throw new Error("The progress element has not been created.");
}
return this._progressElement;

View file

@ -1,8 +1,8 @@
import { IPart, IPartsService } from './parts.service';
import { Notice } from "obsidian";
import { BlitzModalService } from './blitz-modal.service';
import { IWorkspaceService } from './workspace.service';
import { Notice } from 'obsidian';
import type { BlitzModalService } from "./blitz-modal.service";
import type { IPart, IPartsService } from "./parts.service";
import type { IWorkspaceService } from "./workspace.service";
export interface IRibbonIconService {
getCallback: () => Promise<void>;
@ -15,8 +15,8 @@ const svg = `
`;
export class RibbonIconService implements IRibbonIconService {
static readonly id = 'memodack';
static readonly title = 'Memodack';
static readonly id = "memodack";
static readonly title = "Memodack";
static readonly svg = svg;
private workspaceService: IWorkspaceService;
@ -37,7 +37,7 @@ export class RibbonIconService implements IRibbonIconService {
const isReadingMode = this.workspaceService.isReadingMode();
if (!isReadingMode) {
new Notice('Only in Reading Mode.');
new Notice("Only in Reading Mode.");
return;
}
@ -50,12 +50,12 @@ export class RibbonIconService implements IRibbonIconService {
}
if (!parts.length) {
new Notice('No parts provided.');
new Notice("No parts provided.");
return;
}
if (parts.length < 4) {
new Notice('At least 4 parts required.');
new Notice("At least 4 parts required.");
return;
}

View file

@ -1,16 +1,15 @@
import { App, Notice, PluginSettingTab, Setting } from 'obsidian';
import { type App, Notice, PluginSettingTab, Setting } from "obsidian";
import prettyBytes from "pretty-bytes";
import {
ELanguage,
EPlayVariant,
EVoiceOverSpeed,
TMemodackPlugin,
} from '../types';
import { ICacheService } from './cache.service';
import { ISettingsService } from './settings.service';
import { ITranslationService } from './translation.service';
import { ITtsService } from './tts.service';
import prettyBytes from 'pretty-bytes';
type TMemodackPlugin,
} from "../types";
import type { ICacheService } from "./cache.service";
import type { ISettingsService } from "./settings.service";
import type { ITranslationService } from "./translation.service";
import type { ITtsService } from "./tts.service";
export class SettingTabService extends PluginSettingTab {
private readonly plugin: TMemodackPlugin;
@ -50,11 +49,11 @@ export class SettingTabService extends PluginSettingTab {
containerEl.empty();
new Setting(containerEl).setName('Provider (Google)').setHeading();
new Setting(containerEl).setName("Provider (Google)").setHeading();
new Setting(containerEl)
.setName('API Key')
.setDesc('API key for translation and text-to-speech services.')
.setName("API Key")
.setDesc("API key for translation and text-to-speech services.")
.addText((text) => {
text
.setValue(this.settingsService.getApiKey())
@ -64,22 +63,22 @@ export class SettingTabService extends PluginSettingTab {
this.settingsService.setApiKey(value);
await this.plugin.saveSettings();
})
.inputEl.setAttribute('type', 'password');
.inputEl.setAttribute("type", "password");
});
this.checkSettings = new Setting(containerEl)
.setName('Connection')
.setDesc('Check access to services by API key.')
.setName("Connection")
.setDesc("Check access to services by API key.")
.addButton((btn) =>
btn
.setButtonText('Check')
.setButtonText("Check")
.setCta()
.onClick(async () => {
await this.check();
}),
);
new Setting(containerEl).setName('Language').setHeading();
new Setting(containerEl).setName("Language").setHeading();
const options: Record<string, string> = {};
@ -88,8 +87,8 @@ export class SettingTabService extends PluginSettingTab {
});
this.nativeLanguageSettings = new Setting(containerEl)
.setName('Native')
.setDesc('This is the language you speak natively.')
.setName("Native")
.setDesc("This is the language you speak natively.")
.addDropdown((dropdown) => {
dropdown
.addOptions(options)
@ -101,8 +100,8 @@ export class SettingTabService extends PluginSettingTab {
});
this.documentLanguageSettings = new Setting(containerEl)
.setName('Document')
.setDesc('This is the language of the document.')
.setName("Document")
.setDesc("This is the language of the document.")
.addDropdown((dropdown) => {
dropdown
.addOptions(options)
@ -113,38 +112,38 @@ export class SettingTabService extends PluginSettingTab {
});
});
new Setting(containerEl).setName('Voiceover').setHeading();
new Setting(containerEl).setName("Voiceover").setHeading();
this.playbackSpeedSettings = new Setting(containerEl)
.setName('Playback speed')
.setDesc('The speed at which the voiceover will be performed.')
.setName("Playback speed")
.setDesc("The speed at which the voiceover will be performed.")
.addDropdown((dropdown) => {
dropdown
.addOptions({
[EVoiceOverSpeed.Normal]: 'Normal',
[EVoiceOverSpeed.x2]: 'x2',
[EVoiceOverSpeed.x3]: 'x3',
[EVoiceOverSpeed.Normal]: "Normal",
[EVoiceOverSpeed.x2]: "x2",
[EVoiceOverSpeed.x3]: "x3",
})
.setValue(this.settingsService.getVoiceOverSpeed().toString())
.onChange(async (value): Promise<void> => {
this.settingsService.setVoiceOverSpeed(parseInt(value));
this.settingsService.setVoiceOverSpeed(parseInt(value, 10));
await this.plugin.saveSettings();
});
});
new Setting(containerEl).setName('Actions').setHeading();
new Setting(containerEl).setName("Actions").setHeading();
this.playVariantSettings = new Setting(containerEl)
.setName('When pressed play')
.setDesc('Will be voiced when you click on a part.')
.setName("When pressed play")
.setDesc("Will be voiced when you click on a part.")
.addDropdown((dropdown) => {
dropdown
.addOptions({
[EPlayVariant.Nothing]: 'Nothing',
[EPlayVariant.Value]: 'Value',
[EPlayVariant.Translation]: 'Translation',
[EPlayVariant.ValueAndTranslation]: 'Value + Translation',
[EPlayVariant.TranslationAndValue]: 'Translation + Value',
[EPlayVariant.Nothing]: "Nothing",
[EPlayVariant.Value]: "Value",
[EPlayVariant.Translation]: "Translation",
[EPlayVariant.ValueAndTranslation]: "Value + Translation",
[EPlayVariant.TranslationAndValue]: "Translation + Value",
})
.setValue(this.settingsService.getPlayVariant())
.onChange(async (value): Promise<void> => {
@ -153,10 +152,10 @@ export class SettingTabService extends PluginSettingTab {
});
});
new Setting(containerEl).setName('Extra').setHeading();
new Setting(containerEl).setName("Extra").setHeading();
new Setting(containerEl)
.setName('Divider')
.setName("Divider")
.setDesc('Split translation values using the ";" symbol.')
.addToggle((toggle) =>
toggle
@ -167,14 +166,14 @@ export class SettingTabService extends PluginSettingTab {
}),
);
new Setting(containerEl).setName('Optimization').setHeading();
new Setting(containerEl).setName("Optimization").setHeading();
const cacheSetting = new Setting(containerEl)
.setName('Cache')
.setName("Cache")
.setDesc(prettyBytes(this.cacheSize))
.addButton((btn) =>
btn
.setButtonText('Clear')
.setButtonText("Clear")
.setCta()
.onClick(async () => {
await this.cacheService.clear();
@ -191,7 +190,7 @@ export class SettingTabService extends PluginSettingTab {
const apiKey = this.settingsService.getApiKey();
if (!apiKey) {
new Notice('No API key entered.');
new Notice("No API key entered.");
return;
}

View file

@ -1,7 +1,12 @@
import { ELanguage, EPlayVariant, EVoiceOverSpeed, TSettings } from '../types';
import {
ELanguage,
EPlayVariant,
EVoiceOverSpeed,
type TSettings,
} from "../types";
export const defaultSettings: TSettings = {
apiKey: '',
apiKey: "",
source: ELanguage.English,
target: ELanguage.Ukrainian,
playVariant: EPlayVariant.ValueAndTranslation,

View file

@ -1,15 +1,15 @@
import { IConductorService } from './conductor.service';
import { IEditorService } from './editor.service';
import { ISettingsService } from './settings.service';
import { ITranslationService } from './translation.service';
import type { IConductorService } from "./conductor.service";
import type { IEditorService } from "./editor.service";
import type { ISettingsService } from "./settings.service";
import type { ITranslationService } from "./translation.service";
export interface ITranslateCommandService {
getCallback: () => Promise<void>;
}
export class TranslateCommandService implements ITranslateCommandService {
static readonly id = 'translate';
static readonly name = 'Translate';
static readonly id = "translate";
static readonly name = "Translate";
private editorService: IEditorService;
private translationService: ITranslationService;

View file

@ -1,7 +1,6 @@
import { Notice, RequestUrlResponsePromise, requestUrl } from 'obsidian';
import { ELanguage } from '../types';
import { ISettingsService } from './settings.service';
import { Notice, type RequestUrlResponsePromise, requestUrl } from "obsidian";
import { ELanguage } from "../types";
import type { ISettingsService } from "./settings.service";
export interface ITranslationService {
translate(
@ -51,7 +50,7 @@ export class TranslationService implements ITranslationService {
return json.data.translations[0].translatedText || null;
} catch (e) {
console.error(
`Failed to process translation. ${e instanceof Error ? e.message : ''}`,
`Failed to process translation. ${e instanceof Error ? e.message : ""}`,
);
return null;
}
@ -62,18 +61,18 @@ export class TranslationService implements ITranslationService {
const response = await this.translate(
ELanguage.English,
ELanguage.Ukrainian,
'ping',
"ping",
);
if (!response) {
new Notice('The translation service is not working.');
new Notice("The translation service is not working.");
return;
}
new Notice('The translation service is working.');
new Notice("The translation service is working.");
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (e) {
new Notice('The translation service is not working.');
} catch (_e) {
new Notice("The translation service is not working.");
}
}
@ -92,7 +91,7 @@ export class TranslationService implements ITranslationService {
q: text,
source,
target,
format: 'text',
format: "text",
};
return JSON.stringify(body);
@ -100,9 +99,9 @@ export class TranslationService implements ITranslationService {
private request(url: string, body: string): RequestUrlResponsePromise {
return requestUrl({
method: 'POST',
method: "POST",
url,
contentType: 'application/json',
contentType: "application/json",
body,
});
}

View file

@ -1,7 +1,6 @@
import { Notice, RequestUrlResponsePromise, requestUrl } from 'obsidian';
import { ELanguage } from '../types';
import { ISettingsService } from './settings.service';
import { Notice, type RequestUrlResponsePromise, requestUrl } from "obsidian";
import { ELanguage } from "../types";
import type { ISettingsService } from "./settings.service";
export interface ITtsService {
tts(language: ELanguage, value: string): Promise<string | null>;
@ -39,7 +38,7 @@ export class TtsService implements ITtsService {
return json.audioContent || null;
} catch (e) {
console.error(
`Failed to process TTS. ${e instanceof Error ? e.message : ''}`,
`Failed to process TTS. ${e instanceof Error ? e.message : ""}`,
);
return null;
}
@ -47,17 +46,17 @@ export class TtsService implements ITtsService {
async test(): Promise<void> {
try {
const response = await this.tts(ELanguage.English, 'ping');
const response = await this.tts(ELanguage.English, "ping");
if (!response) {
new Notice('The text-to-speech service is not working.');
new Notice("The text-to-speech service is not working.");
return;
}
new Notice('The text-to-speech service is working.');
new Notice("The text-to-speech service is working.");
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (e) {
new Notice('The text-to-speech service is not working.');
} catch (_e) {
new Notice("The text-to-speech service is not working.");
}
}
@ -76,9 +75,9 @@ export class TtsService implements ITtsService {
input: { text: value },
voice: {
languageCode: language,
ssmlGender: 'NEUTRAL',
ssmlGender: "NEUTRAL",
},
audioConfig: { audioEncoding: 'MP3' },
audioConfig: { audioEncoding: "MP3" },
};
return JSON.stringify(body);
@ -86,9 +85,9 @@ export class TtsService implements ITtsService {
private request(url: string, body: string): RequestUrlResponsePromise {
return requestUrl({
method: 'POST',
method: "POST",
url,
contentType: 'application/json',
contentType: "application/json",
body,
});
}

View file

@ -1,4 +1,4 @@
import { TFile, TFolder, Vault } from 'obsidian';
import type { TFile, TFolder, Vault } from "obsidian";
export interface IVaultService {
read(file: TFile): Promise<string>;

View file

@ -1,4 +1,4 @@
import { MarkdownView, TFile, Workspace } from 'obsidian';
import { MarkdownView, type TFile, type Workspace } from "obsidian";
export interface IWorkspaceService {
getActiveFile(): TFile | null;
@ -18,7 +18,7 @@ export class WorkspaceService implements IWorkspaceService {
isReadingMode(): boolean {
return (
this.workspace.getActiveViewOfType(MarkdownView)?.getMode() === 'preview'
this.workspace.getActiveViewOfType(MarkdownView)?.getMode() === "preview"
);
}
}

View file

@ -1,13 +1,13 @@
import { Plugin } from 'obsidian';
import type { Plugin } from "obsidian";
export type TMemodackPlugin = Plugin & { saveSettings: () => Promise<void> };
export enum EPlayVariant {
Nothing = 'nothing',
Value = 'value',
Translation = 'translation',
ValueAndTranslation = 'value-and-translation',
TranslationAndValue = 'translation-and-value',
Nothing = "nothing",
Value = "value",
Translation = "translation",
ValueAndTranslation = "value-and-translation",
TranslationAndValue = "translation-and-value",
}
export enum EVoiceOverSpeed {
@ -17,19 +17,19 @@ export enum EVoiceOverSpeed {
}
export enum ELanguage {
Ukrainian = 'uk',
English = 'en',
MandarinChinese = 'zh-CN',
Hindi = 'hi',
Spanish = 'es',
StandardArabic = 'ar',
French = 'fr',
Bengali = 'bn',
Portuguese = 'pt',
Urdu = 'ur',
Indonesian = 'id',
German = 'de',
Japanese = 'ja',
Ukrainian = "uk",
English = "en",
MandarinChinese = "zh-CN",
Hindi = "hi",
Spanish = "es",
StandardArabic = "ar",
French = "fr",
Bengali = "bn",
Portuguese = "pt",
Urdu = "ur",
Indonesian = "id",
German = "de",
Japanese = "ja",
}
export type TSettings = {

View file

@ -1,3 +1,3 @@
it('should evaluate the condition', () => {
it("should evaluate the condition", () => {
expect(1 + 2).toEqual(3);
});