murashit_codex-panel/tests/mocks/obsidian.ts

484 lines
12 KiB
TypeScript

import Moment from "moment";
import { installObsidianElementHelpers } from "../support/obsidian-dom";
export type App = Record<string, never>;
export const notices: string[] = [];
export const moment = Moment;
export const Platform = {
isDesktop: true,
isMobile: false,
isDesktopApp: true,
isMobileApp: false,
isIosApp: false,
isAndroidApp: false,
isPhone: false,
isTablet: false,
isMacOS: true,
isWin: false,
isLinux: false,
};
export class FileSystemAdapter {
constructor(readonly basePath = "") {}
getBasePath(): string {
return this.basePath;
}
}
export class TFile {
path: string;
basename: string;
name: string;
extension: string;
constructor(path = "", basename = "") {
this.path = path;
this.name = path.split("/").pop() ?? path;
const dotIndex = this.name.lastIndexOf(".");
this.extension = dotIndex === -1 ? "" : this.name.slice(dotIndex + 1);
this.basename = basename || (dotIndex === -1 ? this.name : this.name.slice(0, dotIndex));
}
}
export class Notice {
readonly message: string;
constructor(message: string) {
this.message = message;
notices.push(message);
}
}
export function htmlToMarkdown(html: string | HTMLElement | Document | DocumentFragment): string {
return typeof html === "string" ? html : "";
}
export async function requestUrl(_request: unknown): Promise<{ status: number; text: string }> {
return { status: 200, text: "" };
}
export function prepareFuzzySearch(query: string): (text: string) => { score: number; matches: unknown[] } | null {
const normalizedQuery = query.toLowerCase();
return (text: string) => {
const normalizedText = text.toLowerCase();
if (normalizedQuery.length === 0) return { score: 0, matches: [] };
const startsAt = normalizedText.indexOf(normalizedQuery);
if (startsAt !== -1) {
return { score: 10_000 - startsAt * 10 - normalizedText.length, matches: [[startsAt, startsAt + normalizedQuery.length]] };
}
let textIndex = 0;
let firstMatch = -1;
let lastMatch = -1;
for (const char of normalizedQuery) {
const foundAt = normalizedText.indexOf(char, textIndex);
if (foundAt === -1) return null;
if (firstMatch === -1) firstMatch = foundAt;
lastMatch = foundAt;
textIndex = foundAt + 1;
}
const spread = lastMatch - firstMatch;
return { score: 5_000 - firstMatch * 10 - spread - normalizedText.length, matches: [] };
};
}
export function sortSearchResults(results: { match: { score: number } }[]): void {
results.sort((a, b) => b.match.score - a.match.score);
}
export function stripHeadingForLink(heading: string): string {
return heading.trim();
}
export function getAllTags(cache: { tags?: { tag: string }[]; frontmatter?: { tags?: unknown } }): string[] | null {
const tags = [...(cache.tags?.map((tag) => tag.tag) ?? []), ...frontmatterTags(cache.frontmatter?.tags)];
return tags.length > 0 ? tags : null;
}
function frontmatterTags(value: unknown): string[] {
if (typeof value === "string") return [value];
if (!Array.isArray(value)) return [];
return value.filter((tag): tag is string => typeof tag === "string");
}
export function parseLinktext(linktext: string): { path: string; subpath: string } {
const headingIndex = linktext.indexOf("#");
const blockIndex = linktext.indexOf("^");
const subpathStart = headingIndex === -1 ? blockIndex : blockIndex === -1 ? headingIndex : Math.min(headingIndex, blockIndex);
return subpathStart === -1
? { path: linktext, subpath: "" }
: { path: linktext.slice(0, subpathStart), subpath: linktext.slice(subpathStart) };
}
export function normalizePath(path: string): string {
return path
.replace(/\u00a0/g, " ")
.replace(/[\\/]+/g, "/")
.replace(/^\/+|\/+$/g, "")
.normalize();
}
export class Modal {
readonly contentEl: HTMLElement;
constructor(readonly app: App) {
ensureElementHelpers();
this.contentEl = document.createElement("div");
}
open(): void {
void this.onOpen();
}
close(): void {
this.onClose();
}
onOpen(): Promise<void> | void {
// Test mock placeholder.
}
onClose(): void {
// Test mock placeholder.
}
}
export abstract class SuggestModal<T> extends Modal {
limit = 100;
emptyStateText = "";
readonly inputEl: HTMLInputElement;
readonly resultContainerEl: HTMLElement;
constructor(app: App) {
super(app);
this.inputEl = document.createElement("input");
this.resultContainerEl = document.createElement("div");
}
setPlaceholder(placeholder: string): void {
this.inputEl.placeholder = placeholder;
}
setInstructions(_instructions: { command: string; purpose: string }[]): void {
// Test mock placeholder.
}
selectActiveSuggestion(evt: MouseEvent | KeyboardEvent): void {
const suggestions = this.getSuggestions(this.inputEl.value);
if (!Array.isArray(suggestions)) return;
const suggestion = suggestions.at(0);
if (suggestion) this.onChooseSuggestion(suggestion, evt);
}
abstract getSuggestions(query: string): T[] | Promise<T[]>;
abstract renderSuggestion(value: T, el: HTMLElement): void;
abstract onChooseSuggestion(item: T, evt: MouseEvent | KeyboardEvent): void;
}
export class ItemView {
readonly app: App;
readonly contentEl: HTMLElement;
readonly containerEl: HTMLElement;
constructor(readonly leaf: { app?: App; containerEl?: HTMLElement }) {
ensureElementHelpers();
this.app = leaf.app ?? {};
this.containerEl = leaf.containerEl ?? document.createElement("div");
this.contentEl = this.containerEl.createDiv();
}
registerDomEvent<K extends keyof DocumentEventMap>(element: Document, type: K, callback: (event: DocumentEventMap[K]) => void): void {
element.addEventListener(type, callback);
}
registerEvent(_eventRef: unknown): void {
// Test mock placeholder.
}
getState(): Record<string, unknown> {
return {};
}
setState(_state: unknown, _result: unknown): Promise<void> {
return Promise.resolve();
}
onClose(): Promise<void> | void {
// Test mock placeholder.
}
}
export class MarkdownView {
file: TFile | null = null;
}
export class Plugin {
constructor(readonly app: App) {}
register(_callback: () => void): void {
// Test mock placeholder.
}
registerEvent(_eventRef: unknown): void {
// Test mock placeholder.
}
addCommand(_command: unknown): void {
// Test mock placeholder.
}
addRibbonIcon(_icon: string, _title: string, _callback: () => void): void {
// Test mock placeholder.
}
addSettingTab(_tab: unknown): void {
// Test mock placeholder.
}
registerView(_type: string, _factory: unknown): void {
// Test mock placeholder.
}
loadData(): Promise<unknown> {
return Promise.resolve(null);
}
saveData(_storedValue: unknown): Promise<void> {
return Promise.resolve();
}
}
export const MarkdownRenderer = {
render(_app: unknown, text: string, parent: HTMLElement): Promise<void> {
parent.textContent = text;
return Promise.resolve();
},
};
export class PluginSettingTab {
containerEl: HTMLElement;
constructor(
readonly app: App,
readonly plugin: unknown,
) {
ensureElementHelpers();
this.containerEl = document.createElement("div");
}
display(): void {
// Test mock placeholder.
}
hide(): void {
this.containerEl.empty();
}
}
export class Setting {
readonly settingEl: HTMLDivElement;
readonly infoEl: HTMLDivElement;
readonly controlEl: HTMLDivElement;
descEl: HTMLDivElement;
private nameEl: HTMLDivElement | null = null;
constructor(containerEl: HTMLElement) {
ensureElementHelpers();
this.settingEl = containerEl.createDiv({ cls: "setting-item" });
this.infoEl = this.settingEl.createDiv({ cls: "setting-item-info" });
this.descEl = this.infoEl.createDiv({ cls: "setting-item-description" });
this.controlEl = this.settingEl.createDiv({ cls: "setting-item-control" });
}
setName(name: string): this {
this.nameEl?.remove();
this.nameEl = this.infoEl.createDiv({ cls: "setting-item-name", text: name });
return this;
}
setDesc(desc: string): this {
this.descEl.empty();
this.descEl.textContent = desc;
return this;
}
setClass(cls: string): this {
this.settingEl.addClass(cls);
return this;
}
setHeading(): this {
this.settingEl.addClass("setting-item-heading");
return this;
}
addButton(callback: (button: ButtonComponent) => void): this {
callback(new ButtonComponent(this.controlEl));
return this;
}
addExtraButton(callback: (button: ExtraButtonComponent) => void): this {
callback(new ExtraButtonComponent(this.controlEl));
return this;
}
addDropdown(callback: (dropdown: DropdownComponent) => void): this {
callback(new DropdownComponent(this.controlEl));
return this;
}
addText(callback: (text: TextComponent) => void): this {
callback(new TextComponent(this.controlEl));
return this;
}
addToggle(callback: (toggle: ToggleComponent) => void): this {
callback(new ToggleComponent(this.controlEl));
return this;
}
}
export class ButtonComponent {
readonly buttonEl: HTMLButtonElement;
constructor(parent: HTMLElement) {
this.buttonEl = parent.createEl("button", { attr: { type: "button" } });
}
setButtonText(text: string): this {
this.buttonEl.textContent = text;
return this;
}
setDisabled(disabled: boolean): this {
this.buttonEl.disabled = disabled;
return this;
}
onClick(callback: () => void): this {
this.buttonEl.onclick = callback;
return this;
}
}
export class ExtraButtonComponent {
readonly extraSettingsEl: HTMLElement;
constructor(parent: HTMLElement) {
this.extraSettingsEl = parent.createDiv({ cls: "clickable-icon extra-setting-button" });
this.extraSettingsEl.tabIndex = 0;
}
setDisabled(disabled: boolean): this {
this.extraSettingsEl.classList.toggle("is-disabled", disabled);
this.extraSettingsEl.ariaDisabled = String(disabled);
return this;
}
setTooltip(tooltip: string): this {
this.extraSettingsEl.title = tooltip;
return this;
}
setIcon(icon: string): this {
setIcon(this.extraSettingsEl, icon);
return this;
}
onClick(callback: () => void): this {
this.extraSettingsEl.onclick = callback;
return this;
}
}
export class DropdownComponent {
readonly selectEl: HTMLSelectElement;
constructor(parent: HTMLElement) {
this.selectEl = parent.createEl("select");
this.selectEl.addClass("dropdown");
}
addOption(value: string, label: string): this {
const option = document.createElement("option");
option.value = value;
option.textContent = label;
this.selectEl.append(option);
return this;
}
setValue(value: string): this {
this.selectEl.value = value;
return this;
}
onChange(callback: (value: string) => void | Promise<void>): this {
this.selectEl.onchange = () => {
void callback(this.selectEl.value);
};
return this;
}
}
export class TextComponent {
readonly inputEl: HTMLInputElement;
constructor(parent: HTMLElement) {
this.inputEl = parent.createEl("input");
}
setPlaceholder(value: string): this {
this.inputEl.placeholder = value;
return this;
}
setValue(value: string): this {
this.inputEl.value = value;
return this;
}
onChange(callback: (value: string) => void | Promise<void>): this {
this.inputEl.onchange = () => {
void callback(this.inputEl.value);
};
return this;
}
}
export class ToggleComponent {
readonly toggleEl: HTMLInputElement;
private readonly containerEl: HTMLDivElement;
constructor(parent: HTMLElement) {
this.containerEl = parent.createDiv({ cls: "checkbox-container" });
this.toggleEl = this.containerEl.createEl("input", { attr: { type: "checkbox" } });
}
setValue(value: boolean): this {
this.toggleEl.checked = value;
this.containerEl.classList.toggle("is-enabled", value);
return this;
}
onChange(callback: (value: boolean) => void | Promise<void>): this {
this.toggleEl.onchange = () => {
void callback(this.toggleEl.checked);
};
return this;
}
}
export function setIcon(element: HTMLElement, icon: string): void {
element.dataset["icon"] = icon;
}
function ensureElementHelpers(): void {
installObsidianElementHelpers();
}