Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better Edit Card Modal #989

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 58 additions & 11 deletions src/gui/EditModal.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { App, Modal } from "obsidian";
import { t } from "src/lang/helpers";
import { SRSettings } from "src/settings";
import { includedSeperator } from "src/util/utils";

// from https://github.com/chhoumann/quickadd/blob/bce0b4cdac44b867854d6233796e3406dfd163c6/src/gui/GenericInputPrompt/GenericInputPrompt.ts#L5
export class FlashcardEditModal extends Modal {
public changedText: string;
public waitForClose: Promise<string>;

public title: HTMLDivElement;
public textArea: HTMLTextAreaElement;
public textAreaFront: HTMLTextAreaElement;
public textAreaBack: HTMLTextAreaElement;
public response: HTMLDivElement;
public saveButton: HTMLButtonElement;
public cancelButton: HTMLButtonElement;
Expand All @@ -17,18 +20,42 @@ export class FlashcardEditModal extends Modal {
private rejectPromise: (reason?: any) => void;
private didSaveChanges = false;
private readonly modalText: string;
private textFront: string;
private textBack: string;
private seperator: string;
private multilineSeperator: boolean;

public static Prompt(app: App, placeholder: string): Promise<string> {
const newPromptModal = new FlashcardEditModal(app, placeholder);
public static Prompt(app: App, settings: SRSettings, placeholder: string): Promise<string> {
const newPromptModal = new FlashcardEditModal(app, settings, placeholder);
return newPromptModal.waitForClose;
}

constructor(app: App, existingText: string) {
constructor(app: App, settings: SRSettings, existingText: string) {
super(app);

this.modalText = existingText;
this.changedText = existingText;

// Select the seperator used
this.seperator = includedSeperator(this.modalText, [
settings.singleLineReversedCardSeparator,
settings.multilineReversedCardSeparator,
settings.singleLineCardSeparator,
settings.multilineCardSeparator,
]);
// Split Text based on the Seperator
[this.textFront, this.textBack] = this.modalText.split(this.seperator);
// Trim leading \n for multiline
this.multilineSeperator = this.seperator
? [settings.multilineCardSeparator, settings.multilineReversedCardSeparator].contains(
this.seperator,
)
: false;
if (this.multilineSeperator) {
this.textBack = this.textBack.trimStart();
this.textFront = this.textFront.trimEnd();
}

this.waitForClose = new Promise<string>((resolve, reject) => {
this.resolvePromise = resolve;
this.rejectPromise = reject;
Expand All @@ -52,10 +79,18 @@ export class FlashcardEditModal extends Modal {
this.title.setText(t("EDIT_CARD"));
this.title.addClass("sr-title");

this.textArea = this.contentEl.createEl("textarea");
this.textArea.addClass("sr-input");
this.textArea.setText(this.modalText ?? "");
this.textArea.addEventListener("keydown", this.saveOnEnterCallback);
this.textAreaFront = this.contentEl.createEl("textarea");
this.textAreaFront.addClass("sr-input");
this.textAreaFront.setText(this.textFront ?? "");
this.textAreaFront.addEventListener("keydown", this.saveOnEnterCallback);

// Only for cards with seperator
if (this.seperator) {
this.textAreaBack = this.contentEl.createEl("textarea");
this.textAreaBack.addClass("sr-input");
this.textAreaBack.setText(this.textBack ?? "");
this.textAreaBack.addEventListener("keydown", this.saveOnEnterCallback);
}

this._createResponse(this.contentEl);
}
Expand All @@ -66,7 +101,7 @@ export class FlashcardEditModal extends Modal {
onOpen() {
super.onOpen();

this.textArea.focus();
this.textAreaFront.focus();
}

/**
Expand All @@ -93,7 +128,19 @@ export class FlashcardEditModal extends Modal {

private save() {
this.didSaveChanges = true;
this.changedText = this.textArea.value;
this.changedText = this.textAreaFront.value;
if (this.seperator) {
// New line at end of Front
if (this.multilineSeperator && !this.textAreaFront.value.endsWith("\n")) {
this.changedText += "\n";
}
this.changedText += this.seperator;
// New line at start of Back
if (this.multilineSeperator && !this.textAreaBack.value.startsWith("\n")) {
this.changedText += "\n";
}
this.changedText += this.textAreaBack.value;
}
this.close();
}

Expand All @@ -107,7 +154,7 @@ export class FlashcardEditModal extends Modal {
}

private removeInputListener() {
this.textArea.removeEventListener("keydown", this.saveOnEnterCallback);
this.textAreaFront.removeEventListener("keydown", this.saveOnEnterCallback);
}

// -> Response section
Expand Down
2 changes: 1 addition & 1 deletion src/gui/FlashcardModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export class FlashcardModal extends Modal {
// Just the question/answer text; without any preceding topic tag
const textPrompt = currentQ.questionText.actualQuestion;

const editModal = FlashcardEditModal.Prompt(this.app, textPrompt);
const editModal = FlashcardEditModal.Prompt(this.app, this.settings, textPrompt);
editModal
.then(async (modifiedCardText) => {
this.reviewSequencer.updateCurrentQuestionText(modifiedCardText);
Expand Down
18 changes: 18 additions & 0 deletions src/util/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,3 +219,21 @@ export function parseObsidianFrontmatterTag(tagStr: string): string[] {
}
return result;
}

/**
* Checks a text contains one of the given seperators.
* Priority: Exact match > position in text > order of input seperators
*
* @param text Text to check one of the seperators is included
* @param seperators The seperators to check for
* @returns The first seperator that is included or null if none of them included.
*/
export function includedSeperator(text: string, seperators: string[]): string | null {
// https://www.30secondsofcode.org/js/s/array-has-only-one-match-or-many/
const sep = seperators.filter((value) => text.includes(value));
if (sep.length <= 0) {
return null;
}
sep.sort((a, b) => (a.includes(b) || b.includes(a) ? b.length - a.length : 0));
return sep[0];
}
23 changes: 23 additions & 0 deletions tests/unit/util/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { YAML_FRONT_MATTER_REGEX } from "src/constants";
import {
extractFrontmatter,
findLineIndexOfSearchStringIgnoringWs,
includedSeperator,
literalStringReplace,
} from "src/util/utils";

Expand Down Expand Up @@ -243,3 +244,25 @@ describe("findLineIndexOfSearchStringIgnoringWs", () => {
expect(findLineIndexOfSearchStringIgnoringWs(lines, "??")).toEqual(2);
});
});

describe("includedSperator", () => {
const sep = ["::", ":::", "?", "??"];

test("No Match", () => {
expect(includedSeperator("Question!!Answer", sep)).toBe(null);
expect(includedSeperator("Question:Answer", sep)).toBe(null);
});
test("No sep", () => {
expect(includedSeperator("Question!!Answer", [])).toBe(null);
});
test("One Match", () => {
expect(includedSeperator("Question::Answer", sep)).toBe("::");
expect(includedSeperator("Question?Answer", sep)).toBe("?");
});
test("Multiple Match", () => {
expect(includedSeperator("Question::Answer?", sep)).toBe("::");
expect(includedSeperator("Question:::Answer::", sep)).toBe(":::");
expect(includedSeperator("Question??Answer::", sep)).toBe("::");
expect(includedSeperator("Question??Answer?", sep)).toBe("??");
});
});
Loading