refactor: words service

This commit is contained in:
memodack 2025-11-24 17:20:50 +02:00
parent ba3d9f4baf
commit f798ff03a8
6 changed files with 62 additions and 56 deletions

View file

@ -26,6 +26,7 @@ export default class MemodackPlugin extends Plugin {
this.addCommand({
id: "translate",
name: "Translate",
icon: "languages",
editorCallback: (editor) => {
register.registerEditor(editor);
return register.getTranslateCommandService().getCallback();

View file

@ -34,9 +34,9 @@ export class RandomService implements IRandomService {
}
getSegment(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];
const items = text.split(divider).map((item) => item.trim());
const index = Math.floor(Math.random() * items.length);
return items[index];
}
getNumbers(maxNumber: number, ignoreNumber: number, count: number): number[] {

View file

@ -16,7 +16,6 @@ import { GoogleTranslationService } from "./services/google-translation.service"
import { GoogleTtsService } from "./services/google-tts.service";
import { ManifestService } from "./services/manifest.service";
import { type IMppService, MppService } from "./services/mpp.service";
import { PartsService } from "./services/parts.service";
import { PathsService } from "./services/paths.service";
import { PlayerService } from "./services/player.service";
import { type IRibbonIconService, RibbonIconService } from "./services/ribbon-icon.service";
@ -27,6 +26,7 @@ import { type ITranslateCommandService, TranslateCommandService } from "./servic
import { TranslationService } from "./services/translation.service";
import { TtsService } from "./services/tts.service";
import { VaultService } from "./services/vault.service";
import { WordsService } from "./services/words.service";
import { WorkspaceService } from "./services/workspace.service";
import type { TMemodackPlugin } from "./types";
@ -68,7 +68,7 @@ export class Register implements IRegister {
["IConductorService", ConductorService],
["IMppService", MppService],
["ITranslationService", TranslationService],
["IPartsService", PartsService],
["IWordsService", WordsService],
["IPlayerService", PlayerService],
["IAudioService", AudioService],
["ITtsService", TtsService],

View file

@ -38,9 +38,9 @@ export class MppService implements IMppService {
const fragment = document.createDocumentFragment();
const parts = node.nodeValue.split(/(\{.*?\|.*?\})/);
const words = node.nodeValue.split(/(\{.*?\|.*?\})/);
parts.forEach((part) => {
words.forEach((part) => {
const match = part.match(/\{(.*?)\|(.*?)\}/);
if (match) {

View file

@ -3,7 +3,7 @@ import { inject, singleton } from "tsyringe";
import type { IPracticeModalService } from "../practice/practice-modal.service";
import type { IQuestsService } from "../practice/quests.service";
import type { IWord } from "../types";
import type { IPart, IPartsService } from "./parts.service";
import type { IWordsService } from "./words.service";
import type { IWorkspaceService } from "./workspace.service";
export interface IRibbonIconService {
@ -22,7 +22,7 @@ export class RibbonIconService implements IRibbonIconService {
constructor(
@inject("IWorkspaceService")
private readonly workspaceService: IWorkspaceService,
@inject("IPartsService") private readonly partsService: IPartsService,
@inject("IWordsService") private readonly wordsService: IWordsService,
@inject("IPracticeModalService")
private readonly practiceModalService: IPracticeModalService,
@inject("IQuestsService")
@ -41,31 +41,24 @@ export class RibbonIconService implements IRibbonIconService {
return;
}
let parts: IPart[] = [];
let words: IWord[] = [];
parts = await this.partsService.getSelectedParts();
words = await this.wordsService.getSelectedWords();
if (!parts.length) {
parts = await this.partsService.getParts();
if (!words.length) {
words = await this.wordsService.getWords();
}
if (!parts.length) {
new Notice("No parts provided.");
if (!words.length) {
new Notice("No words provided.");
return;
}
if (parts.length < 4) {
new Notice("At least 4 parts required.");
if (words.length < 4) {
new Notice("At least 4 words required.");
return;
}
const words: IWord[] = parts.map((p) => ({
value: p.value,
translation: p.translation,
text: p?.text || null,
imageUrl: p?.imageUrl || null,
}));
this.questsService.createQuests(words);
this.practiceModalService.open();
};

View file

@ -1,18 +1,12 @@
import { TFile } from "obsidian";
import { inject, singleton } from "tsyringe";
import type { IWord } from "../types";
import type { IVaultService } from "./vault.service";
import type { IWorkspaceService } from "./workspace.service";
export interface IPart {
value: string;
translation: string;
text?: string;
imageUrl?: string;
}
export interface IPartsService {
getParts(): Promise<IPart[]>;
getSelectedParts(): Promise<IPart[]>;
export interface IWordsService {
getWords(): Promise<IWord[]>;
getSelectedWords(): Promise<IWord[]>;
}
interface IExternalLink {
@ -21,14 +15,14 @@ interface IExternalLink {
}
@singleton()
export class PartsService implements IPartsService {
export class WordsService implements IWordsService {
constructor(
@inject("IVaultService") private readonly vaultService: IVaultService,
@inject("IWorkspaceService")
private readonly workspaceService: IWorkspaceService,
) {}
async getParts(): Promise<IPart[]> {
async getWords(): Promise<IWord[]> {
const activeFile = this.workspaceService.getActiveFile();
if (!activeFile) {
@ -41,16 +35,23 @@ export class PartsService implements IPartsService {
return [];
}
const matches = [...content.matchAll(/\{([^\\|{}]+)\|([^\\|{}]+)\}/g)]; // {value|translation}
/**
* {value|translation}
*/
const matches = [...content.matchAll(/\{([^\\|{}]+)\|([^\\|{}]+)\}/g)];
const parts: IPart[] = [];
const words: IWord[] = [];
const texts = content.split("\n");
// Generate parts array
/**
* Generate words array
*/
matches.forEach((match) => {
// Don't put duplicates to the array
if (parts.find((item) => item.value === match[1])) {
/**
* Don't put duplicates to the array
*/
if (words.find((item) => item.value === match[1])) {
return;
}
@ -58,16 +59,17 @@ export class PartsService implements IPartsService {
let rawText = texts[textIndex].replaceAll(`{${match[1]}|${match[2]}}`, match[1]);
const anotherParts = [...rawText.matchAll(/\{([^\\|{}]+)\|([^\\|{}]+)\}/g)];
const anotherWords = [...rawText.matchAll(/\{([^\\|{}]+)\|([^\\|{}]+)\}/g)];
anotherParts.forEach((item) => {
anotherWords.forEach((item) => {
rawText = rawText.replace(item[0], item[1]);
});
const data: IPart = {
const data: IWord = {
value: match[1],
translation: match[2],
text: rawText,
imageUrl: null,
};
/**
@ -79,13 +81,13 @@ export class PartsService implements IPartsService {
data.imageUrl = imageUrl;
}
parts.push(data);
words.push(data);
});
return parts;
return words;
}
async getSelectedParts(): Promise<IPart[]> {
async getSelectedWords(): Promise<IWord[]> {
const activeFile = this.workspaceService.getActiveFile();
if (!activeFile) {
@ -94,7 +96,7 @@ export class PartsService implements IPartsService {
const content = await this.vaultService.read(activeFile);
const parts: IPart[] = [];
const words: IWord[] = [];
const selection = window.getSelection();
@ -102,12 +104,16 @@ export class PartsService implements IPartsService {
const ranges = selection.getRangeAt(0);
const spans = document.querySelectorAll(".memodack___syntax");
// Iterate over all <span> elements
/**
* Iterate over all <span> elements
*/
spans.forEach((span) => {
const spanRange = document.createRange();
spanRange.selectNode(span);
// Check if the selection intersects with the <span> element
/**
* Check if the selection intersects with the <span> element
*/
if (ranges.intersectsNode(span)) {
const value = span.textContent;
@ -117,9 +123,11 @@ export class PartsService implements IPartsService {
const translation = span.getAttribute("data-translation");
// Format the string and add it to the array
/**
* Format the string and add it to the array
*/
if (value && translation) {
const data: IPart = { value, translation };
const data: IWord = { value, translation, text: null, imageUrl: null };
/**
* Image
@ -130,15 +138,17 @@ export class PartsService implements IPartsService {
data.imageUrl = imageUrl;
}
parts.push(data);
words.push(data);
}
}
});
// Clear selection
/**
* Clear selection
*/
selection.removeAllRanges();
return parts;
return words;
}
return [];
@ -160,7 +170,9 @@ export class PartsService implements IPartsService {
}
private extractExternalLinks(content: string): IExternalLink[] {
// [text](url)
/**
* [text](url)
*/
const regex = /\[([^\]]+)\]\(([^)]+)\)/g;
return [...content.matchAll(regex)].map((m) => ({