mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-01-23 21:47:11 +00:00
814 lines
23 KiB
TypeScript
814 lines
23 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-unsafe-function-type */
|
|
export const SettingCache = new Map<any, any>();
|
|
//@ts-ignore obsidian global
|
|
globalThis.activeDocument = document;
|
|
|
|
declare const hostPlatform: string | undefined;
|
|
|
|
// import { interceptFetchForLogging } from "../harness/utils/intercept";
|
|
// interceptFetchForLogging();
|
|
globalThis.process = {
|
|
platform: (hostPlatform || "win32") as any,
|
|
} as any;
|
|
console.warn(`[Obsidian Mock] process.platform is set to ${globalThis.process.platform}`);
|
|
export class TAbstractFile {
|
|
vault: Vault;
|
|
path: string;
|
|
name: string;
|
|
parent: TFolder | null;
|
|
|
|
constructor(vault: Vault, path: string, name: string, parent: TFolder | null) {
|
|
this.vault = vault;
|
|
this.path = path;
|
|
this.name = name;
|
|
this.parent = parent;
|
|
}
|
|
}
|
|
|
|
export class TFile extends TAbstractFile {
|
|
stat: {
|
|
ctime: number;
|
|
mtime: number;
|
|
size: number;
|
|
} = { ctime: Date.now(), mtime: Date.now(), size: 0 };
|
|
|
|
get extension(): string {
|
|
return this.name.split(".").pop() || "";
|
|
}
|
|
|
|
get basename(): string {
|
|
const parts = this.name.split(".");
|
|
if (parts.length > 1) parts.pop();
|
|
return parts.join(".");
|
|
}
|
|
}
|
|
|
|
export class TFolder extends TAbstractFile {
|
|
children: TAbstractFile[] = [];
|
|
|
|
get isRoot(): boolean {
|
|
return this.path === "" || this.path === "/";
|
|
}
|
|
}
|
|
|
|
export class EventRef {}
|
|
|
|
// class StorageMap<T, U> extends Map<T, U> {
|
|
// constructor(saveName?: string) {
|
|
// super();
|
|
// if (saveName) {
|
|
// this.saveName = saveName;
|
|
// void this.restore(saveName);
|
|
// }
|
|
// }
|
|
// private saveName: string = "";
|
|
// async restore(saveName: string) {
|
|
// this.saveName = saveName;
|
|
// const db = await OpenKeyValueDatabase(saveName);
|
|
// const data = await db.get<{ [key: string]: U }>("data");
|
|
// if (data) {
|
|
// for (const key of Object.keys(data)) {
|
|
// this.set(key as any as T, data[key]);
|
|
// }
|
|
// }
|
|
// db.close();
|
|
// return this;
|
|
// }
|
|
// saving: boolean = false;
|
|
// async save() {
|
|
// if (this.saveName === "") {
|
|
// return;
|
|
// }
|
|
// if (this.saving) {
|
|
// return;
|
|
// }
|
|
// try {
|
|
// this.saving = true;
|
|
|
|
// const db = await OpenKeyValueDatabase(this.saveName);
|
|
// const data: { [key: string]: U } = {};
|
|
// for (const [key, value] of this.entries()) {
|
|
// data[key as any as string] = value;
|
|
// }
|
|
// await db.set("data", data);
|
|
// db.close();
|
|
// } finally {
|
|
// this.saving = false;
|
|
// }
|
|
// }
|
|
// set(key: T, value: U): this {
|
|
// super.set(key, value);
|
|
// void this.save();
|
|
// return this;
|
|
// }
|
|
|
|
// }
|
|
|
|
export class Vault {
|
|
adapter: DataAdapter;
|
|
vaultName: string = "MockVault";
|
|
private files: Map<string, TAbstractFile> = new Map();
|
|
private contents: Map<string, string | ArrayBuffer> = new Map();
|
|
private root: TFolder;
|
|
private listeners: Map<string, Set<Function>> = new Map();
|
|
|
|
constructor(vaultName?: string) {
|
|
if (vaultName) {
|
|
this.vaultName = vaultName;
|
|
this.files = new Map();
|
|
this.contents = new Map();
|
|
}
|
|
this.adapter = new DataAdapter(this);
|
|
this.root = new TFolder(this, "", "", null);
|
|
this.files.set("", this.root);
|
|
}
|
|
|
|
getAbstractFileByPath(path: string): TAbstractFile | null {
|
|
if (path === "/") path = "";
|
|
const file = this.files.get(path);
|
|
return file || null;
|
|
}
|
|
getAbstractFileByPathInsensitive(path: string): TAbstractFile | null {
|
|
const lowerPath = path.toLowerCase();
|
|
for (const [p, file] of this.files.entries()) {
|
|
if (p.toLowerCase() === lowerPath) {
|
|
return file;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
getFiles(): TFile[] {
|
|
return Array.from(this.files.values()).filter((f) => f instanceof TFile);
|
|
}
|
|
|
|
async _adapterRead(path: string): Promise<string | null> {
|
|
await Promise.resolve();
|
|
const file = this.contents.get(path);
|
|
if (typeof file === "string") {
|
|
return file;
|
|
}
|
|
if (file instanceof ArrayBuffer) {
|
|
return new TextDecoder().decode(file);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
async _adapterReadBinary(path: string): Promise<ArrayBuffer | null> {
|
|
await Promise.resolve();
|
|
const file = this.contents.get(path);
|
|
if (file instanceof ArrayBuffer) {
|
|
return file;
|
|
}
|
|
if (typeof file === "string") {
|
|
return new TextEncoder().encode(file).buffer;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
async read(file: TFile): Promise<string> {
|
|
await Promise.resolve();
|
|
const content = this.contents.get(file.path);
|
|
if (typeof content === "string") return content;
|
|
if (content instanceof ArrayBuffer) {
|
|
return new TextDecoder().decode(content);
|
|
}
|
|
return "";
|
|
}
|
|
|
|
async readBinary(file: TFile): Promise<ArrayBuffer> {
|
|
await Promise.resolve();
|
|
const content = this.contents.get(file.path);
|
|
if (content instanceof ArrayBuffer) return content;
|
|
if (typeof content === "string") {
|
|
return new TextEncoder().encode(content).buffer;
|
|
}
|
|
return new ArrayBuffer(0);
|
|
}
|
|
|
|
private async _create(path: string, data: string | ArrayBuffer, options?: DataWriteOptions): Promise<TFile> {
|
|
if (this.files.has(path)) throw new Error("File already exists");
|
|
const name = path.split("/").pop() || "";
|
|
const parentPath = path.includes("/") ? path.substring(0, path.lastIndexOf("/")) : "";
|
|
let parent = this.getAbstractFileByPath(parentPath);
|
|
if (!parent || !(parent instanceof TFolder)) {
|
|
parent = await this.createFolder(parentPath);
|
|
}
|
|
|
|
const file = new TFile(this, path, name, parent as TFolder);
|
|
file.stat.size = typeof data === "string" ? new TextEncoder().encode(data).length : data.byteLength;
|
|
file.stat.ctime = options?.ctime ?? Date.now();
|
|
file.stat.mtime = options?.mtime ?? Date.now();
|
|
this.files.set(path, file);
|
|
this.contents.set(path, data);
|
|
(parent as TFolder).children.push(file);
|
|
// console.dir(this.files);
|
|
|
|
this.trigger("create", file);
|
|
return file;
|
|
}
|
|
async create(path: string, data: string, options?: DataWriteOptions): Promise<TFile> {
|
|
return await this._create(path, data, options);
|
|
}
|
|
async createBinary(path: string, data: ArrayBuffer, options?: DataWriteOptions): Promise<TFile> {
|
|
return await this._create(path, data, options);
|
|
}
|
|
|
|
async _modify(file: TFile, data: string | ArrayBuffer, options?: DataWriteOptions): Promise<void> {
|
|
await Promise.resolve();
|
|
this.contents.set(file.path, data);
|
|
file.stat.mtime = options?.mtime ?? Date.now();
|
|
file.stat.ctime = options?.ctime ?? file.stat.ctime ?? Date.now();
|
|
file.stat.size = typeof data === "string" ? data.length : data.byteLength;
|
|
this.files.set(file.path, file);
|
|
this.trigger("modify", file);
|
|
}
|
|
async modify(file: TFile, data: string, options?: DataWriteOptions): Promise<void> {
|
|
return await this._modify(file, data, options);
|
|
}
|
|
async modifyBinary(file: TFile, data: ArrayBuffer, options?: DataWriteOptions): Promise<void> {
|
|
return await this._modify(file, data, options);
|
|
}
|
|
|
|
async createFolder(path: string): Promise<TFolder> {
|
|
if (path === "") return this.root;
|
|
if (this.files.has(path)) {
|
|
const f = this.files.get(path);
|
|
if (f instanceof TFolder) return f;
|
|
throw new Error("Path is a file");
|
|
}
|
|
const name = path.split("/").pop() || "";
|
|
const parentPath = path.includes("/") ? path.substring(0, path.lastIndexOf("/")) : "";
|
|
const parent = await this.createFolder(parentPath);
|
|
const folder = new TFolder(this, path, name, parent);
|
|
this.files.set(path, folder);
|
|
parent.children.push(folder);
|
|
return folder;
|
|
}
|
|
|
|
async delete(file: TAbstractFile, force?: boolean): Promise<void> {
|
|
await Promise.resolve();
|
|
this.files.delete(file.path);
|
|
this.contents.delete(file.path);
|
|
if (file.parent) {
|
|
file.parent.children = file.parent.children.filter((c) => c !== file);
|
|
}
|
|
this.trigger("delete", file);
|
|
}
|
|
|
|
async trash(file: TAbstractFile, system: boolean): Promise<void> {
|
|
await Promise.resolve();
|
|
return this.delete(file);
|
|
}
|
|
|
|
on(name: string, callback: (...args: any[]) => any, ctx?: any): EventRef {
|
|
if (!this.listeners.has(name)) {
|
|
this.listeners.set(name, new Set());
|
|
}
|
|
const boundCallback = ctx ? callback.bind(ctx) : callback;
|
|
this.listeners.get(name)!.add(boundCallback);
|
|
return { name, callback: boundCallback } as any;
|
|
}
|
|
|
|
off(name: string, callback: any) {
|
|
this.listeners.get(name)?.delete(callback);
|
|
}
|
|
|
|
offref(ref: EventRef) {
|
|
const { name, callback } = ref as any;
|
|
this.off(name, callback);
|
|
}
|
|
|
|
trigger(name: string, ...args: any[]) {
|
|
this.listeners.get(name)?.forEach((cb) => cb(...args));
|
|
}
|
|
|
|
getName(): string {
|
|
return SettingCache.get(this) || "MockVault";
|
|
}
|
|
}
|
|
|
|
export class DataAdapter {
|
|
vault: Vault;
|
|
constructor(vault: Vault) {
|
|
this.vault = vault;
|
|
}
|
|
stat(path: string): Promise<{ ctime: number; mtime: number; size: number }> {
|
|
const file = this.vault.getAbstractFileByPath(path);
|
|
if (file && file instanceof TFile) {
|
|
return Promise.resolve({
|
|
ctime: file.stat.ctime,
|
|
mtime: file.stat.mtime,
|
|
size: file.stat.size,
|
|
});
|
|
}
|
|
return Promise.reject(new Error("File not found"));
|
|
}
|
|
async list(path: string): Promise<{ files: string[]; folders: string[] }> {
|
|
await Promise.resolve();
|
|
const abstractFile = this.vault.getAbstractFileByPath(path);
|
|
if (abstractFile instanceof TFolder) {
|
|
const files: string[] = [];
|
|
const folders: string[] = [];
|
|
for (const child of abstractFile.children) {
|
|
if (child instanceof TFile) files.push(child.path);
|
|
else if (child instanceof TFolder) folders.push(child.path);
|
|
}
|
|
return { files, folders };
|
|
}
|
|
return { files: [], folders: [] };
|
|
}
|
|
async _write(path: string, data: string | ArrayBuffer, options?: DataWriteOptions): Promise<void> {
|
|
const file = this.vault.getAbstractFileByPath(path);
|
|
if (file instanceof TFile) {
|
|
if (typeof data === "string") {
|
|
await this.vault.modify(file, data, options);
|
|
} else {
|
|
await this.vault.modifyBinary(file, data, options);
|
|
}
|
|
} else {
|
|
if (typeof data === "string") {
|
|
await this.vault.create(path, data, options);
|
|
} else {
|
|
await this.vault.createBinary(path, data, options);
|
|
}
|
|
}
|
|
}
|
|
async write(path: string, data: string, options?: DataWriteOptions): Promise<void> {
|
|
return await this._write(path, data, options);
|
|
}
|
|
async writeBinary(path: string, data: ArrayBuffer, options?: DataWriteOptions): Promise<void> {
|
|
return await this._write(path, data, options);
|
|
}
|
|
|
|
async read(path: string): Promise<string> {
|
|
const file = this.vault.getAbstractFileByPath(path);
|
|
if (file instanceof TFile) return await this.vault.read(file);
|
|
throw new Error("File not found");
|
|
}
|
|
async readBinary(path: string): Promise<ArrayBuffer> {
|
|
const file = this.vault.getAbstractFileByPath(path);
|
|
if (file instanceof TFile) return await this.vault.readBinary(file);
|
|
throw new Error("File not found");
|
|
}
|
|
|
|
async exists(path: string): Promise<boolean> {
|
|
await Promise.resolve();
|
|
return this.vault.getAbstractFileByPath(path) !== null;
|
|
}
|
|
async mkdir(path: string): Promise<void> {
|
|
await this.vault.createFolder(path);
|
|
}
|
|
async remove(path: string): Promise<void> {
|
|
const file = this.vault.getAbstractFileByPath(path);
|
|
if (file) await this.vault.delete(file);
|
|
}
|
|
}
|
|
|
|
class Events {
|
|
_eventEmitter = new EventTarget();
|
|
_events = new Map<any, any>();
|
|
_eventTarget(cb: any) {
|
|
const x = this._events.get(cb);
|
|
if (x) {
|
|
return x;
|
|
}
|
|
const callback = (evt: any) => {
|
|
x(evt?.detail ?? undefined);
|
|
};
|
|
this._events.set(cb, callback);
|
|
return callback;
|
|
}
|
|
on(name: string, cb: any, ctx?: any) {
|
|
this._eventEmitter.addEventListener(name, this._eventTarget(cb));
|
|
}
|
|
trigger(name: string, args: any) {
|
|
const evt = new CustomEvent(name, {
|
|
detail: args,
|
|
});
|
|
this._eventEmitter.dispatchEvent(evt);
|
|
}
|
|
}
|
|
|
|
class Workspace extends Events {
|
|
getActiveFile() {
|
|
return null;
|
|
}
|
|
getMostRecentLeaf() {
|
|
return null;
|
|
}
|
|
|
|
onLayoutReady(cb: any) {
|
|
// cb();
|
|
// console.log("[Obsidian Mock] Workspace onLayoutReady registered");
|
|
// this._eventEmitter.addEventListener("layout-ready", () => {
|
|
// console.log("[Obsidian Mock] Workspace layout-ready event triggered");
|
|
setTimeout(() => {
|
|
cb();
|
|
}, 200);
|
|
// });
|
|
}
|
|
getLeavesOfType() {
|
|
return [];
|
|
}
|
|
getLeaf() {
|
|
return { setViewState: () => Promise.resolve(), revealLeaf: () => Promise.resolve() };
|
|
}
|
|
revealLeaf() {
|
|
return Promise.resolve();
|
|
}
|
|
containerEl: HTMLElement = document.createElement("div");
|
|
}
|
|
export class App {
|
|
vaultName: string = "MockVault";
|
|
constructor(vaultName?: string) {
|
|
if (vaultName) {
|
|
this.vaultName = vaultName;
|
|
}
|
|
this.vault = new Vault(this.vaultName);
|
|
}
|
|
vault: Vault;
|
|
workspace: Workspace = new Workspace();
|
|
metadataCache: any = {
|
|
on: (name: string, cb: any, ctx?: any) => {},
|
|
getFileCache: () => null,
|
|
};
|
|
}
|
|
|
|
export class Plugin {
|
|
app: App;
|
|
manifest: any;
|
|
settings: any;
|
|
commands: Map<string, any> = new Map();
|
|
constructor(app: App, manifest: any) {
|
|
this.app = app;
|
|
this.manifest = manifest;
|
|
}
|
|
async loadData(): Promise<any> {
|
|
await Promise.resolve();
|
|
return SettingCache.get(this.app) ?? {};
|
|
}
|
|
async saveData(data: any): Promise<void> {
|
|
await Promise.resolve();
|
|
SettingCache.set(this.app, data);
|
|
}
|
|
onload() {}
|
|
onunload() {}
|
|
addSettingTab(tab: any) {}
|
|
addCommand(command: any) {
|
|
this.commands.set(command.id, command);
|
|
}
|
|
addStatusBarItem() {
|
|
return {
|
|
setText: () => {},
|
|
setClass: () => {},
|
|
addClass: () => {},
|
|
};
|
|
}
|
|
addRibbonIcon() {
|
|
const icon = {
|
|
setAttribute: () => icon,
|
|
addClass: () => icon,
|
|
onclick: () => {},
|
|
};
|
|
return icon;
|
|
}
|
|
registerView(type: string, creator: any) {}
|
|
registerObsidianProtocolHandler(handler: any) {}
|
|
registerEvent(handler: any) {}
|
|
registerDomEvent(target: any, eventName: string, handler: any) {}
|
|
}
|
|
|
|
export class Notice {
|
|
constructor(message: string) {
|
|
console.log("Notice:", message);
|
|
}
|
|
}
|
|
|
|
export class Modal {
|
|
app: App;
|
|
contentEl: HTMLElement;
|
|
titleEl: HTMLElement;
|
|
|
|
constructor(app: App) {
|
|
this.app = app;
|
|
this.contentEl = document.createElement("div");
|
|
this.titleEl = document.createElement("div");
|
|
}
|
|
open() {
|
|
this.onOpen();
|
|
}
|
|
close() {
|
|
this.onClose();
|
|
}
|
|
onOpen() {}
|
|
onClose() {}
|
|
setPlaceholder(p: string) {}
|
|
setTitle(t: string) {}
|
|
}
|
|
|
|
export class PluginSettingTab {
|
|
app: App;
|
|
plugin: Plugin;
|
|
containerEl: HTMLElement;
|
|
constructor(app: App, plugin: Plugin) {
|
|
this.app = app;
|
|
this.plugin = plugin;
|
|
this.containerEl = document.createElement("div");
|
|
}
|
|
display() {}
|
|
}
|
|
|
|
export function normalizePath(path: string): string {
|
|
return path.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
}
|
|
|
|
export const Platform = {
|
|
isDesktop: true,
|
|
isMobile: false,
|
|
};
|
|
|
|
export class Menu {
|
|
addItem(cb: (item: MenuItem) => any) {
|
|
cb(new MenuItem());
|
|
return this;
|
|
}
|
|
showAtMouseEvent(evt: MouseEvent) {}
|
|
}
|
|
export class MenuItem {
|
|
setTitle(title: string) {
|
|
return this;
|
|
}
|
|
setIcon(icon: string) {
|
|
return this;
|
|
}
|
|
onClick(cb: (evt: MouseEvent) => any) {
|
|
return this;
|
|
}
|
|
}
|
|
export class MenuSeparator {}
|
|
|
|
export class Component {
|
|
load() {}
|
|
unload() {}
|
|
}
|
|
|
|
export class ButtonComponent extends Component {
|
|
buttonEl: HTMLButtonElement = document.createElement("button");
|
|
setButtonText(text: string) {
|
|
return this;
|
|
}
|
|
setCta() {
|
|
return this;
|
|
}
|
|
onClick(cb: any) {
|
|
return this;
|
|
}
|
|
setClass(c: string) {
|
|
return this;
|
|
}
|
|
}
|
|
|
|
export class TextComponent extends Component {
|
|
inputEl: HTMLInputElement = document.createElement("input");
|
|
onChange(cb: any) {
|
|
return this;
|
|
}
|
|
setValue(v: string) {
|
|
return this;
|
|
}
|
|
}
|
|
|
|
export class ToggleComponent extends Component {
|
|
onChange(cb: any) {
|
|
return this;
|
|
}
|
|
setValue(v: boolean) {
|
|
return this;
|
|
}
|
|
}
|
|
|
|
export class DropdownComponent extends Component {
|
|
addOption(v: string, d: string) {
|
|
return this;
|
|
}
|
|
addOptions(o: any) {
|
|
return this;
|
|
}
|
|
onChange(cb: any) {
|
|
return this;
|
|
}
|
|
setValue(v: string) {
|
|
return this;
|
|
}
|
|
}
|
|
|
|
export class SliderComponent extends Component {
|
|
onChange(cb: any) {
|
|
return this;
|
|
}
|
|
setValue(v: number) {
|
|
return this;
|
|
}
|
|
}
|
|
|
|
export class Setting {
|
|
nameEl: HTMLElement;
|
|
descEl: HTMLElement;
|
|
controlEl: HTMLElement;
|
|
infoEl: HTMLElement;
|
|
|
|
constructor(containerEl: HTMLElement) {
|
|
this.nameEl = containerEl.createDiv();
|
|
this.descEl = containerEl.createDiv();
|
|
this.controlEl = containerEl.createDiv();
|
|
this.infoEl = containerEl.createDiv();
|
|
}
|
|
setName(name: string) {
|
|
return this;
|
|
}
|
|
setDesc(desc: string) {
|
|
return this;
|
|
}
|
|
setClass(c: string) {
|
|
return this;
|
|
}
|
|
addText(cb: (text: TextComponent) => any) {
|
|
cb(new TextComponent());
|
|
return this;
|
|
}
|
|
addToggle(cb: (toggle: ToggleComponent) => any) {
|
|
cb(new ToggleComponent());
|
|
return this;
|
|
}
|
|
addButton(cb: (btn: ButtonComponent) => any) {
|
|
cb(new ButtonComponent());
|
|
return this;
|
|
}
|
|
addDropdown(cb: (dropdown: DropdownComponent) => any) {
|
|
cb(new DropdownComponent());
|
|
return this;
|
|
}
|
|
addSlider(cb: (slider: SliderComponent) => any) {
|
|
cb(new SliderComponent());
|
|
return this;
|
|
}
|
|
}
|
|
|
|
// HTMLElement extensions
|
|
if (typeof HTMLElement !== "undefined") {
|
|
const proto = HTMLElement.prototype as any;
|
|
proto.createDiv = function (o?: any) {
|
|
const div = document.createElement("div");
|
|
if (o?.cls) div.addClass(o.cls);
|
|
if (o?.text) div.setText(o.text);
|
|
this.appendChild(div);
|
|
return div;
|
|
};
|
|
proto.createEl = function (tag: string, o?: any) {
|
|
const el = document.createElement(tag);
|
|
if (o?.cls) el.addClass(o.cls);
|
|
if (o?.text) el.setText(o.text);
|
|
this.appendChild(el);
|
|
return el;
|
|
};
|
|
proto.createSpan = function (o?: any) {
|
|
return this.createEl("span", o);
|
|
};
|
|
proto.empty = function () {
|
|
this.innerHTML = "";
|
|
};
|
|
proto.setText = function (t: string) {
|
|
this.textContent = t;
|
|
};
|
|
proto.addClass = function (c: string) {
|
|
this.classList.add(c);
|
|
};
|
|
proto.removeClass = function (c: string) {
|
|
this.classList.remove(c);
|
|
};
|
|
proto.toggleClass = function (c: string, b: boolean) {
|
|
this.classList.toggle(c, b);
|
|
};
|
|
proto.hasClass = function (c: string) {
|
|
return this.classList.contains(c);
|
|
};
|
|
}
|
|
|
|
export class Editor {}
|
|
|
|
export class FuzzySuggestModal<T> {
|
|
constructor(app: App) {}
|
|
setPlaceholder(p: string) {}
|
|
open() {}
|
|
close() {}
|
|
private __dummy(_: T): never {
|
|
throw new Error("Not implemented.");
|
|
}
|
|
}
|
|
export class MarkdownRenderer {
|
|
static render(app: App, md: string, el: HTMLElement, path: string, component: Component) {
|
|
el.innerHTML = md;
|
|
return Promise.resolve();
|
|
}
|
|
}
|
|
export class MarkdownView {}
|
|
export class TextAreaComponent extends Component {}
|
|
export class ItemView {}
|
|
export class WorkspaceLeaf {}
|
|
|
|
export function sanitizeHTMLToDom(html: string) {
|
|
const div = document.createElement("div");
|
|
div.innerHTML = html;
|
|
return div;
|
|
}
|
|
|
|
export function addIcon() {}
|
|
export const debounce = (fn: any) => fn;
|
|
export async function request(options: any) {
|
|
const result = await requestUrl(options);
|
|
return result.text;
|
|
}
|
|
|
|
export async function requestUrl({
|
|
body,
|
|
headers,
|
|
method,
|
|
url,
|
|
contentType,
|
|
}: RequestUrlParam): Promise<RequestUrlResponse> {
|
|
// console.log("[requestUrl] Mock called:", { method, url, contentType });
|
|
const reqHeadersObj: Record<string, string> = {};
|
|
for (const key of Object.keys(headers || {})) {
|
|
reqHeadersObj[key.toLowerCase()] = headers[key];
|
|
}
|
|
if (contentType) {
|
|
reqHeadersObj["content-type"] = contentType;
|
|
}
|
|
reqHeadersObj["Cache-Control"] = "no-cache, no-store, must-revalidate";
|
|
reqHeadersObj["Pragma"] = "no-cache";
|
|
reqHeadersObj["Expires"] = "0";
|
|
const result = await fetch(url, {
|
|
method: method,
|
|
headers: {
|
|
...reqHeadersObj,
|
|
},
|
|
|
|
body: body,
|
|
});
|
|
const headersObj: Record<string, string> = {};
|
|
result.headers.forEach((value, key) => {
|
|
headersObj[key] = value;
|
|
});
|
|
let json = undefined;
|
|
let text = undefined;
|
|
let arrayBuffer = undefined;
|
|
try {
|
|
const isJson = result.headers.get("content-type")?.includes("application/json");
|
|
arrayBuffer = await result.arrayBuffer();
|
|
const isText = result.headers.get("content-type")?.startsWith("text/");
|
|
if (isText || isJson) {
|
|
text = new TextDecoder().decode(arrayBuffer);
|
|
}
|
|
if (isJson) {
|
|
json = await JSON.parse(text || "{}");
|
|
}
|
|
} catch (e) {
|
|
console.warn("Failed to parse response:", e);
|
|
// ignore
|
|
}
|
|
return {
|
|
status: result.status,
|
|
headers: headersObj,
|
|
text: text,
|
|
json: json,
|
|
arrayBuffer: arrayBuffer,
|
|
};
|
|
}
|
|
export function stringifyYaml(obj: any) {
|
|
return JSON.stringify(obj);
|
|
}
|
|
export function parseYaml(s: string) {
|
|
return JSON.parse(s);
|
|
}
|
|
export function getLanguage() {
|
|
return "en";
|
|
}
|
|
export function setIcon(el: HTMLElement, icon: string) {}
|
|
export function arrayBufferToBase64(buffer: ArrayBuffer): string {
|
|
return btoa(String.fromCharCode(...new Uint8Array(buffer)));
|
|
}
|
|
export function base64ToArrayBuffer(base64: string): ArrayBuffer {
|
|
return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)).buffer;
|
|
}
|
|
|
|
export type DataWriteOptions = any;
|
|
export type PluginManifest = any;
|
|
export type RequestUrlParam = any;
|
|
export type RequestUrlResponse = any;
|
|
export type MarkdownFileInfo = any;
|
|
export type ListedFiles = {
|
|
files: string[];
|
|
folders: string[];
|
|
};
|