diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/esbuild.config.mjs b/esbuild.config.mjs new file mode 100644 index 0000000..c1d1a41 --- /dev/null +++ b/esbuild.config.mjs @@ -0,0 +1,41 @@ +import esbuild from "esbuild"; +import process from "process"; +import builtins from "builtin-modules"; + +const banner = `/* +THIS IS A GENERATED/BUNDLED FILE BY ESBUILD +If you want to view the source, please visit the GitHub repository of this plugin. +*/ +`; + +const prod = process.argv[2] === "production"; + +esbuild + .build({ + banner: { js: banner }, + entryPoints: ["src/main.ts"], + bundle: true, + 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, + ], + format: "cjs", + target: "es2018", + logLevel: "info", + sourcemap: prod ? false : "inline", + treeShaking: true, + outfile: "main.js", + }) + .catch(() => process.exit(1)); diff --git a/main.js b/main.js new file mode 100644 index 0000000..d5b14c3 --- /dev/null +++ b/main.js @@ -0,0 +1,969 @@ +/* +THIS IS A GENERATED/BUNDLED FILE BY ESBUILD +If you want to view the source, please visit the GitHub repository of this plugin. +*/ + +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// src/main.ts +var main_exports = {}; +__export(main_exports, { + default: () => VaultHubPlugin +}); +module.exports = __toCommonJS(main_exports); +var import_obsidian5 = require("obsidian"); + +// src/settings.ts +var import_obsidian = require("obsidian"); +var DEFAULT_SETTINGS = { + githubToken: "", + defaultCategories: [], + vaultHubUrl: "http://localhost:3000", + publishedResources: [] +}; +var VaultHubSettingTab = class extends import_obsidian.PluginSettingTab { + constructor(app, plugin) { + super(app, plugin); + this.plugin = plugin; + } + display() { + const { containerEl } = this; + containerEl.empty(); + containerEl.createEl("h2", { text: "Vault Hub Settings" }); + new import_obsidian.Setting(containerEl).setName("GitHub Personal Access Token").setDesc("Token used to create repos and push files. Requires 'repo' scope.").addText( + (text) => text.setPlaceholder("ghp_xxxxxxxxxxxx").setValue(this.plugin.settings.githubToken).then((t) => { + t.inputEl.type = "password"; + t.inputEl.style.width = "300px"; + }).onChange(async (value) => { + this.plugin.settings.githubToken = value; + await this.plugin.saveSettings(); + }) + ); + new import_obsidian.Setting(containerEl).setName("Vault Hub URL").setDesc("URL of the Vault Hub website (default: http://localhost:3000 for development).").addText( + (text) => text.setPlaceholder("http://localhost:3000").setValue(this.plugin.settings.vaultHubUrl).onChange(async (value) => { + this.plugin.settings.vaultHubUrl = value; + await this.plugin.saveSettings(); + }) + ); + new import_obsidian.Setting(containerEl).setName("Default Categories").setDesc("Comma-separated list of default categories for new publications.").addText( + (text) => text.setPlaceholder("appearance, workflow").setValue(this.plugin.settings.defaultCategories.join(", ")).onChange(async (value) => { + this.plugin.settings.defaultCategories = value.split(",").map((s) => s.trim()).filter((s) => s.length > 0); + await this.plugin.saveSettings(); + }) + ); + } +}; + +// src/modals/PublishModal.ts +var import_obsidian2 = require("obsidian"); + +// src/detection.ts +var MD_PATTERNS = [ + [/```dataviewjs/m, "dataview"], + [/```dataview/m, "dataview"], + [/```datacore/m, "datacore"], + [/```tasks/m, "obsidian-tasks-plugin"], + [/```kanban/m, "obsidian-kanban"], + [/```excalidraw/m, "obsidian-excalidraw-plugin"], + [/```chart/m, "obsidian-charts"], + [/```cards-timeline/m, "any-block"], + [/```button/m, "buttons"], + [/```meta-bind/m, "obsidian-meta-bind-plugin"], + [/`=\s*this\./m, "dataview"], + [/`=\s*dv\./m, "dataview"], + [/`\$=\s*dv\./m, "dataview"], + [/<%\s*tp\./m, "templater-obsidian"], + [/<%[-_*]?\s/m, "templater-obsidian"], + [/kanban-plugin:/m, "obsidian-kanban"], + [/^banner:/m, "obsidian-banners"] +]; +var CSS_PATTERNS = [ + [/\.dataview/i, "dataview"], + [/\.kanban/i, "obsidian-kanban"], + [/\.tasks-plugin/i, "obsidian-tasks-plugin"], + [/\.excalidraw/i, "obsidian-excalidraw-plugin"], + [/\.cm-table-widget/i, "table-editor-obsidian"] +]; +async function getInstalledPlugins(vault) { + const plugins = []; + const pluginsDir = `${vault.configDir}/plugins`; + const listing = await vault.adapter.list(pluginsDir); + for (const folder of listing.folders) { + const manifestPath = `${folder}/manifest.json`; + try { + const raw = await vault.adapter.read(manifestPath); + const manifest = JSON.parse(raw); + plugins.push({ + id: manifest.id, + name: manifest.name, + version: manifest.version, + author: manifest.author, + autoDetected: false + }); + } catch (e) { + } + } + return plugins; +} +async function detectPlugins(fileContent, fileType, vault) { + const installed = await getInstalledPlugins(vault); + const installedIds = new Set(installed.map((p) => p.id)); + const detectedIds = /* @__PURE__ */ new Set(); + const patterns = fileType === "css" ? CSS_PATTERNS : MD_PATTERNS; + for (const [regex, pluginId] of patterns) { + if (regex.test(fileContent) && installedIds.has(pluginId)) { + detectedIds.add(pluginId); + } + } + return installed.map((p) => ({ + ...p, + autoDetected: detectedIds.has(p.id) + })); +} + +// src/github.ts +var GitHubAPI = class { + constructor(token) { + this.token = token; + } + async request(path, options = {}) { + const res = await fetch(`https://api.github.com${path}`, { + ...options, + headers: { + Authorization: `token ${this.token}`, + Accept: "application/vnd.github.v3+json", + "Content-Type": "application/json", + ...options.headers + } + }); + if (!res.ok) { + const body = await res.text(); + throw new Error(`GitHub API ${res.status}: ${body}`); + } + return res.json(); + } + async getUser() { + return this.request("/user"); + } + async createRepo(name, description) { + return this.request("/user/repos", { + method: "POST", + body: JSON.stringify({ + name, + description, + auto_init: false, + private: false + }) + }); + } + async addTopics(owner, repo, topics) { + return this.request(`/repos/${owner}/${repo}/topics`, { + method: "PUT", + body: JSON.stringify({ names: topics }) + }); + } + async createFile(owner, repo, path, content, message) { + const encoded = btoa(unescape(encodeURIComponent(content))); + return this.request(`/repos/${owner}/${repo}/contents/${path}`, { + method: "PUT", + body: JSON.stringify({ message, content: encoded }) + }); + } + async updateFile(owner, repo, path, content, message, sha) { + const encoded = btoa(unescape(encodeURIComponent(content))); + return this.request(`/repos/${owner}/${repo}/contents/${path}`, { + method: "PUT", + body: JSON.stringify({ message, content: encoded, sha }) + }); + } + async getFileContent(owner, repo, path) { + try { + const data = await this.request( + `/repos/${owner}/${repo}/contents/${path}` + ); + return { sha: data.sha, content: atob(data.content) }; + } catch (e) { + return null; + } + } +}; + +// src/hubyml.ts +function generateHubYml(data) { + const lines = []; + lines.push("schema: 1"); + lines.push(`type: ${data.type}`); + lines.push(`name: "${esc(data.name)}"`); + lines.push(`tagline: "${esc(data.tagline)}"`); + lines.push(`description: |`); + data.description.split("\n").forEach((l) => lines.push(` ${l}`)); + lines.push(`author: ${data.author}`); + lines.push("categories:"); + data.categories.forEach((c) => lines.push(` - ${c}`)); + if (data.tags.length > 0) { + lines.push("tags:"); + data.tags.forEach((t) => lines.push(` - ${t}`)); + } + if (data.compatibleThemes.length > 0) { + lines.push("compatible_themes:"); + data.compatibleThemes.forEach((t) => lines.push(` - ${t}`)); + } + if (data.screenshots.length > 0) { + lines.push("screenshots:"); + data.screenshots.forEach((s) => lines.push(` - ${s}`)); + } + const selected = data.plugins.filter((p) => p.autoDetected); + if (selected.length > 0) { + lines.push("plugins:"); + selected.forEach((p) => { + lines.push(` - id: ${p.id}`); + lines.push(` name: ${p.name}`); + lines.push(` version: ${p.version}`); + }); + } + lines.push("environment:"); + lines.push(` obsidian_version: "${data.obsidianVersion}"`); + lines.push(` theme: ${data.theme}`); + lines.push(` os: ${data.os}`); + lines.push("files:"); + data.files.forEach((f) => { + lines.push(` - path: ${f.path}`); + lines.push(` type: ${f.type}`); + lines.push(` size: ${f.size}`); + }); + return lines.join("\n") + "\n"; +} +function esc(s) { + return s.replace(/"/g, '\\"'); +} + +// src/readme.ts +function generateReadme(data) { + var _a; + const lines = []; + lines.push(`# ${data.name}`); + lines.push(""); + lines.push(data.tagline); + lines.push(""); + lines.push("## Description"); + lines.push(""); + lines.push(data.description); + lines.push(""); + const selected = data.plugins.filter((p) => p.autoDetected); + if (selected.length > 0) { + lines.push("## Required Plugins"); + lines.push(""); + lines.push("| Plugin | Version | Link |"); + lines.push("|--------|---------|------|"); + selected.forEach((p) => { + lines.push( + `| ${p.name} | ${p.version} | [GitHub](https://github.com/search?q=${encodeURIComponent(p.name + " obsidian")}) |` + ); + }); + lines.push(""); + } + lines.push("## Installation"); + lines.push(""); + if (data.type === "snippet") { + lines.push( + `1. Download \`${((_a = data.files[0]) == null ? void 0 : _a.path) || "snippet.css"}\` from this repo` + ); + lines.push( + "2. Place it in your vault's `.obsidian/snippets/` folder" + ); + lines.push("3. Enable it in Settings > Appearance > CSS Snippets"); + } else { + lines.push("1. Download the `.md` file(s) from this repo"); + lines.push("2. Place them in your vault"); + if (selected.length > 0) { + lines.push("3. Install the required plugins listed above"); + } + } + lines.push(""); + lines.push("---"); + lines.push("*Published via [Vault Hub](https://vaulthub.dev)*"); + lines.push(""); + return lines.join("\n"); +} + +// src/modals/PublishModal.ts +var CATEGORIES = { + snippet: [ + "ui-tweak", + "theme-override", + "layout", + "typography", + "color-scheme", + "editor", + "sidebar", + "publishing" + ], + note: [ + "dashboard", + "tracker", + "query", + "daily-note", + "project-template", + "kanban", + "database", + "automation" + ], + bundle: [ + "dashboard", + "tracker", + "query", + "project-template", + "automation" + ] +}; +var PublishModal = class extends import_obsidian2.Modal { + constructor(app, plugin) { + super(app); + this.step = 1; + // Step 1 + this.resourceType = "snippet"; + this.selectedFiles = []; + // Step 2 + this.allPlugins = []; + this.checkedPlugins = /* @__PURE__ */ new Set(); + // Step 3 + this.name = ""; + this.tagline = ""; + this.description = ""; + this.categories = []; + this.tags = ""; + this.compatibleThemes = []; + // Step 4 + this.readmeContent = ""; + this.plugin = plugin; + } + onOpen() { + this.renderStep(); + } + onClose() { + this.contentEl.empty(); + } + renderStep() { + this.contentEl.empty(); + this.contentEl.addClass("vault-hub-modal"); + const header = this.contentEl.createDiv("vault-hub-header"); + header.createEl("h2", { text: `Publish Resource \u2014 Step ${this.step} of 5` }); + const progress = header.createDiv("vault-hub-progress"); + for (let i = 1; i <= 5; i++) { + const dot = progress.createSpan("vault-hub-dot"); + if (i === this.step) + dot.addClass("active"); + else if (i < this.step) + dot.addClass("done"); + } + switch (this.step) { + case 1: + this.renderStep1(); + break; + case 2: + this.renderStep2(); + break; + case 3: + this.renderStep3(); + break; + case 4: + this.renderStep4(); + break; + case 5: + this.renderStep5(); + break; + } + } + renderStep1() { + const c = this.contentEl; + new import_obsidian2.Setting(c).setName("Resource Type").addDropdown((dd) => { + dd.addOption("snippet", "CSS Snippet"); + dd.addOption("note", "Note / Template"); + dd.addOption("bundle", "Bundle (multiple files)"); + dd.setValue(this.resourceType); + dd.onChange((v) => { + this.resourceType = v; + this.selectedFiles = []; + this.renderStep(); + }); + }); + const ext = this.resourceType === "snippet" ? ".css" : ".md"; + const files = this.app.vault.getFiles().filter((f) => f.extension === ext.slice(1)).sort((a, b) => a.path.localeCompare(b.path)); + const fileSection = c.createDiv(); + fileSection.createEl("h4", { text: `Select file${this.resourceType === "bundle" ? "s" : ""}` }); + const list = fileSection.createDiv("vault-hub-file-list"); + files.forEach((f) => { + const row = list.createDiv("vault-hub-file-row"); + const cb = row.createEl("input", { type: this.resourceType === "bundle" ? "checkbox" : "radio" }); + cb.name = "vault-hub-file"; + cb.checked = this.selectedFiles.includes(f); + cb.addEventListener("change", () => { + if (this.resourceType === "bundle") { + if (cb.checked) + this.selectedFiles.push(f); + else + this.selectedFiles = this.selectedFiles.filter((x) => x !== f); + } else { + this.selectedFiles = cb.checked ? [f] : []; + } + }); + row.createSpan({ text: f.path }); + }); + this.addNav(c, null, () => { + if (this.selectedFiles.length === 0) { + new import_obsidian2.Notice("Select at least one file"); + return false; + } + return true; + }); + } + async renderStep2() { + const c = this.contentEl; + c.createEl("h4", { text: "Select Required Plugins" }); + c.createEl("p", { + text: "Auto-detected plugins are pre-checked. Review and adjust.", + cls: "vault-hub-hint" + }); + const loading = c.createDiv({ text: "Scanning..." }); + const content = await this.app.vault.read(this.selectedFiles[0]); + const fileType = this.resourceType === "snippet" ? "css" : "md"; + this.allPlugins = await detectPlugins(content, fileType, this.app.vault); + loading.remove(); + this.allPlugins.forEach((p) => { + if (p.autoDetected) + this.checkedPlugins.add(p.id); + }); + const list = c.createDiv("vault-hub-plugin-list"); + this.allPlugins.forEach((p) => { + const row = list.createDiv("vault-hub-plugin-row"); + const cb = row.createEl("input", { type: "checkbox" }); + cb.checked = this.checkedPlugins.has(p.id); + cb.addEventListener("change", () => { + if (cb.checked) + this.checkedPlugins.add(p.id); + else + this.checkedPlugins.delete(p.id); + }); + const info = row.createDiv("vault-hub-plugin-info"); + info.createSpan({ text: p.name, cls: "vault-hub-plugin-name" }); + info.createSpan({ text: ` v${p.version}`, cls: "vault-hub-plugin-version" }); + if (p.autoDetected) { + info.createSpan({ text: " (auto-detected)", cls: "vault-hub-auto-badge" }); + } + }); + if (this.allPlugins.length === 0) { + c.createEl("p", { text: "No community plugins installed.", cls: "vault-hub-hint" }); + } + this.addNav(c, null, null); + } + renderStep3() { + const c = this.contentEl; + new import_obsidian2.Setting(c).setName("Name").addText((t) => { + t.setPlaceholder("My Resource").setValue(this.name); + t.onChange((v) => this.name = v); + t.inputEl.style.width = "100%"; + }); + new import_obsidian2.Setting(c).setName("Tagline").setDesc("One-line summary").addText((t) => { + t.setPlaceholder("A brief description").setValue(this.tagline); + t.onChange((v) => this.tagline = v); + t.inputEl.style.width = "100%"; + }); + new import_obsidian2.Setting(c).setName("Description").addTextArea((t) => { + t.setPlaceholder("Detailed description...").setValue(this.description); + t.onChange((v) => this.description = v); + t.inputEl.style.width = "100%"; + t.inputEl.style.minHeight = "80px"; + }); + const cats = CATEGORIES[this.resourceType] || []; + new import_obsidian2.Setting(c).setName("Category").addDropdown((dd) => { + dd.addOption("", "Select..."); + cats.forEach((cat) => dd.addOption(cat, cat)); + dd.setValue(this.categories[0] || ""); + dd.onChange((v) => this.categories = v ? [v] : []); + }); + new import_obsidian2.Setting(c).setName("Tags").setDesc("Comma-separated").addText((t) => { + t.setPlaceholder("glass, blur, dark").setValue(this.tags); + t.onChange((v) => this.tags = v); + }); + if (this.resourceType === "snippet") { + new import_obsidian2.Setting(c).setName("Compatible Themes").addDropdown((dd) => { + dd.addOption("any", "Any theme"); + ["minimal", "velocity", "obsidian-default", "catppuccin"].forEach( + (t) => dd.addOption(t, t) + ); + dd.setValue(this.compatibleThemes[0] || "any"); + dd.onChange((v) => this.compatibleThemes = [v]); + }); + } + this.addNav(c, null, () => { + if (!this.name.trim()) { + new import_obsidian2.Notice("Name is required"); + return false; + } + const selected = this.allPlugins.filter((p) => this.checkedPlugins.has(p.id)).map((p) => ({ ...p, autoDetected: true })); + const readmeData = { + name: this.name, + tagline: this.tagline, + description: this.description, + type: this.resourceType, + plugins: selected, + files: this.selectedFiles.map((f) => ({ path: f.name })) + }; + this.readmeContent = generateReadme(readmeData); + return true; + }); + } + renderStep4() { + const c = this.contentEl; + c.createEl("h4", { text: "Edit README" }); + c.createEl("p", { + text: "Auto-generated from your details. Edit freely.", + cls: "vault-hub-hint" + }); + const textarea = c.createEl("textarea", { cls: "vault-hub-readme-editor" }); + textarea.value = this.readmeContent; + textarea.addEventListener("input", () => { + this.readmeContent = textarea.value; + }); + this.addNav(c, null, null); + } + renderStep5() { + const c = this.contentEl; + c.createEl("h4", { text: "Review & Publish" }); + const summary = c.createDiv("vault-hub-summary"); + summary.createEl("p", { text: `Type: ${this.resourceType}` }); + summary.createEl("p", { text: `Name: ${this.name}` }); + summary.createEl("p", { text: `Files: ${this.selectedFiles.map((f) => f.name).join(", ")}` }); + const selPlugins = this.allPlugins.filter((p) => this.checkedPlugins.has(p.id)); + summary.createEl("p", { + text: `Plugins: ${selPlugins.length > 0 ? selPlugins.map((p) => p.name).join(", ") : "None"}` + }); + summary.createEl("p", { text: `Categories: ${this.categories.join(", ") || "None"}` }); + const btnContainer = c.createDiv("vault-hub-nav"); + const backBtn = btnContainer.createEl("button", { text: "Back" }); + backBtn.addEventListener("click", () => { + this.step--; + this.renderStep(); + }); + const publishBtn = btnContainer.createEl("button", { + text: "Publish", + cls: "mod-cta" + }); + publishBtn.addEventListener("click", () => this.doPublish()); + } + async doPublish() { + var _a; + const token = this.plugin.settings.githubToken; + if (!token) { + new import_obsidian2.Notice("Set your GitHub token in Vault Hub settings first"); + return; + } + const c = this.contentEl; + c.empty(); + c.createEl("h3", { text: "Publishing..." }); + const status = c.createEl("p", { text: "Creating repository..." }); + try { + const gh = new GitHubAPI(token); + const user = await gh.getUser(); + const slug = this.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, ""); + const repoName = `obsidian-${this.resourceType}-${slug}`; + status.setText("Creating repository..."); + const repo = await gh.createRepo(repoName, this.tagline || this.name); + const [owner, rName] = repo.full_name.split("/"); + const topicMap = { + snippet: "obsidian-css-snippet", + note: "obsidian-note-template", + bundle: "obsidian-note-template" + }; + status.setText("Adding topic tag..."); + await gh.addTopics(owner, rName, [topicMap[this.resourceType]]); + for (const file of this.selectedFiles) { + status.setText(`Uploading ${file.name}...`); + const content = await this.app.vault.read(file); + await gh.createFile(owner, rName, file.name, content, `Add ${file.name}`); + } + status.setText("Generating hub.yml..."); + const selectedPlugins = this.allPlugins.filter((p) => this.checkedPlugins.has(p.id)).map((p) => ({ ...p, autoDetected: true })); + const obsVer = this.app.appVersion || "unknown"; + const themeName = ((_a = this.app.vault.config) == null ? void 0 : _a.cssTheme) || "default"; + const hubData = { + type: this.resourceType, + name: this.name, + tagline: this.tagline, + description: this.description, + author: user.login, + categories: this.categories, + tags: this.tags.split(",").map((t) => t.trim()).filter(Boolean), + compatibleThemes: this.compatibleThemes, + screenshots: [], + plugins: selectedPlugins, + obsidianVersion: obsVer, + theme: themeName, + os: navigator.platform, + files: this.selectedFiles.map((f) => ({ + path: f.name, + type: f.extension, + size: f.stat.size + })) + }; + const hubYml = generateHubYml(hubData); + await gh.createFile(owner, rName, "hub.yml", hubYml, "Add hub.yml"); + status.setText("Uploading README..."); + await gh.createFile(owner, rName, "README.md", this.readmeContent, "Add README"); + this.plugin.settings.publishedResources.push({ + repoFullName: repo.full_name, + localFilePath: this.selectedFiles[0].path, + type: this.resourceType, + lastPublishedAt: (/* @__PURE__ */ new Date()).toISOString() + }); + await this.plugin.saveSettings(); + c.empty(); + c.createEl("h3", { text: "Published!" }); + c.createEl("p", { text: `Repository: ${repo.full_name}` }); + const link = c.createEl("a", { + text: repo.html_url, + href: repo.html_url + }); + link.setAttr("target", "_blank"); + c.createEl("p", { + text: "It will appear on Vault Hub within 6 hours.", + cls: "vault-hub-hint" + }); + const closeBtn = c.createEl("button", { text: "Close", cls: "mod-cta" }); + closeBtn.addEventListener("click", () => this.close()); + new import_obsidian2.Notice(`Published to ${repo.full_name}!`); + } catch (e) { + c.empty(); + c.createEl("h3", { text: "Error" }); + c.createEl("p", { text: String(e) }); + const retryBtn = c.createEl("button", { text: "Back to Review" }); + retryBtn.addEventListener("click", () => { + this.step = 5; + this.renderStep(); + }); + } + } + addNav(container, backCheck, nextCheck) { + const nav = container.createDiv("vault-hub-nav"); + if (this.step > 1) { + const back = nav.createEl("button", { text: "Back" }); + back.addEventListener("click", () => { + if (backCheck && !backCheck()) + return; + this.step--; + this.renderStep(); + }); + } + if (this.step < 5) { + const next = nav.createEl("button", { text: "Next", cls: "mod-cta" }); + next.addEventListener("click", () => { + if (nextCheck && !nextCheck()) + return; + this.step++; + this.renderStep(); + }); + } + } +}; + +// src/modals/UpdateModal.ts +var import_obsidian3 = require("obsidian"); +var UpdateModal = class extends import_obsidian3.Modal { + constructor(app, plugin) { + super(app); + this.selected = null; + this.plugin = plugin; + } + onOpen() { + this.render(); + } + onClose() { + this.contentEl.empty(); + } + render() { + const c = this.contentEl; + c.empty(); + c.createEl("h2", { text: "Update Published Resource" }); + const resources = this.plugin.settings.publishedResources; + if (resources.length === 0) { + c.createEl("p", { text: "No published resources yet. Use Publish first." }); + return; + } + new import_obsidian3.Setting(c).setName("Select Resource").addDropdown((dd) => { + dd.addOption("", "Choose..."); + resources.forEach((r, i) => { + dd.addOption(String(i), `${r.repoFullName} (${r.type})`); + }); + dd.onChange((v) => { + this.selected = v ? resources[parseInt(v)] : null; + }); + }); + const updateBtn = c.createEl("button", { text: "Push Update", cls: "mod-cta" }); + updateBtn.addEventListener("click", () => this.doUpdate()); + } + async doUpdate() { + if (!this.selected) { + new import_obsidian3.Notice("Select a resource first"); + return; + } + const token = this.plugin.settings.githubToken; + if (!token) { + new import_obsidian3.Notice("Set your GitHub token in settings first"); + return; + } + const c = this.contentEl; + c.empty(); + c.createEl("h3", { text: "Updating..." }); + try { + const gh = new GitHubAPI(token); + const [owner, repo] = this.selected.repoFullName.split("/"); + const file = this.app.vault.getAbstractFileByPath(this.selected.localFilePath); + if (!file) { + new import_obsidian3.Notice(`File not found: ${this.selected.localFilePath}`); + return; + } + const content = await this.app.vault.read(file); + const existing = await gh.getFileContent(owner, repo, file.name); + if (existing) { + await gh.updateFile( + owner, + repo, + file.name, + content, + `Update ${file.name}`, + existing.sha + ); + } else { + await gh.createFile(owner, repo, file.name, content, `Add ${file.name}`); + } + this.selected.lastPublishedAt = (/* @__PURE__ */ new Date()).toISOString(); + await this.plugin.saveSettings(); + c.empty(); + c.createEl("h3", { text: "Updated!" }); + c.createEl("p", { text: `Pushed to ${this.selected.repoFullName}` }); + const closeBtn = c.createEl("button", { text: "Close", cls: "mod-cta" }); + closeBtn.addEventListener("click", () => this.close()); + new import_obsidian3.Notice("Resource updated!"); + } catch (e) { + c.empty(); + c.createEl("h3", { text: "Error" }); + c.createEl("p", { text: String(e) }); + } + } +}; + +// src/views/BrowseView.ts +var import_obsidian4 = require("obsidian"); +var VIEW_TYPE_BROWSE = "vault-hub-browse"; +var SUPABASE_URL = "https://oxvxiqiushhpwzqtpzdg.supabase.co"; +var SUPABASE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im94dnhpcWl1c2hocHd6cXRwemRnIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzU0MTI4NjAsImV4cCI6MjA5MDk4ODg2MH0.jOJloJvJmV2Q-xuY1wBbUjg0s7DnRGt6htycj4HBtEA"; +var BrowseView = class extends import_obsidian4.ItemView { + constructor(leaf, plugin) { + super(leaf); + this.searchQuery = ""; + this.resources = []; + this.plugin = plugin; + } + getViewType() { + return VIEW_TYPE_BROWSE; + } + getDisplayText() { + return "Vault Hub"; + } + getIcon() { + return "globe"; + } + async onOpen() { + this.render(); + await this.loadResources(); + } + async onClose() { + this.contentEl.empty(); + } + render() { + const c = this.contentEl; + c.empty(); + c.addClass("vault-hub-browse"); + const header = c.createDiv("vault-hub-browse-header"); + header.createEl("h3", { text: "Vault Hub" }); + const searchRow = header.createDiv("vault-hub-search-row"); + const input = searchRow.createEl("input", { + type: "text", + placeholder: "Search resources..." + }); + input.value = this.searchQuery; + input.addEventListener("input", () => { + this.searchQuery = input.value; + }); + input.addEventListener("keydown", (e) => { + if (e.key === "Enter") + this.loadResources(); + }); + const searchBtn = searchRow.createEl("button", { text: "Search" }); + searchBtn.addEventListener("click", () => this.loadResources()); + c.createDiv("vault-hub-results"); + } + async loadResources() { + const resultsEl = this.contentEl.querySelector(".vault-hub-results"); + if (!resultsEl) + return; + resultsEl.empty(); + resultsEl.createEl("p", { text: "Loading...", cls: "vault-hub-hint" }); + try { + let url = `${SUPABASE_URL}/rest/v1/resources?select=id,type,title,owner,repo_name,full_name,tagline,stars,vote_count&is_active=eq.true&order=trending_score.desc&limit=30`; + if (this.searchQuery.trim()) { + url += `&or=(title.ilike.*${encodeURIComponent(this.searchQuery)}*,description.ilike.*${encodeURIComponent(this.searchQuery)}*)`; + } + const res = await fetch(url, { + headers: { + apikey: SUPABASE_KEY, + Authorization: `Bearer ${SUPABASE_KEY}` + } + }); + this.resources = await res.json(); + this.renderResults(resultsEl); + } catch (e) { + resultsEl.empty(); + resultsEl.createEl("p", { text: `Error: ${e}` }); + } + } + renderResults(container) { + container.empty(); + if (this.resources.length === 0) { + container.createEl("p", { + text: "No resources found.", + cls: "vault-hub-hint" + }); + return; + } + this.resources.forEach((r) => { + const card = container.createDiv("vault-hub-result-card"); + const typeColors = { + vault: "#7f6df2", + snippet: "#22d3ee", + note: "#fb923c" + }; + const badge = card.createSpan("vault-hub-type-badge"); + badge.setText(r.type); + badge.style.backgroundColor = typeColors[r.type] || "#666"; + const title = card.createEl("h4"); + title.setText(r.title); + if (r.tagline) { + card.createEl("p", { text: r.tagline, cls: "vault-hub-hint" }); + } + const meta = card.createDiv("vault-hub-result-meta"); + meta.createSpan({ text: `${r.stars} stars` }); + meta.createSpan({ text: `${r.vote_count} votes` }); + meta.createSpan({ text: r.owner }); + const actions = card.createDiv("vault-hub-result-actions"); + const installBtn = actions.createEl("button", { text: "Install" }); + installBtn.addEventListener("click", () => this.installResource(r)); + const ghBtn = actions.createEl("button", { text: "GitHub" }); + ghBtn.addEventListener("click", () => { + window.open(`https://github.com/${r.full_name}`, "_blank"); + }); + }); + } + async installResource(r) { + try { + const res = await fetch( + `https://api.github.com/repos/${r.full_name}/contents`, + { headers: { Accept: "application/vnd.github.v3+json" } } + ); + const files = await res.json(); + if (r.type === "snippet") { + const cssFile = files.find( + (f) => f.name.endsWith(".css") + ); + if (cssFile) { + const raw = await fetch(cssFile.download_url); + const content = await raw.text(); + const snippetPath = `${this.app.vault.configDir}/snippets/${cssFile.name}`; + await this.app.vault.adapter.write(snippetPath, content); + new import_obsidian4.Notice(`Installed snippet: ${cssFile.name}`); + } + } else { + const mdFiles = files.filter( + (f) => f.name.endsWith(".md") && f.name !== "README.md" + ); + for (const f of mdFiles) { + const raw = await fetch(f.download_url); + const content = await raw.text(); + await this.app.vault.create(f.name, content); + new import_obsidian4.Notice(`Installed: ${f.name}`); + } + } + } catch (e) { + new import_obsidian4.Notice(`Install failed: ${e}`); + } + } +}; + +// src/main.ts +var VaultHubPlugin = class extends import_obsidian5.Plugin { + constructor() { + super(...arguments); + this.settings = DEFAULT_SETTINGS; + } + async onload() { + await this.loadSettings(); + this.registerView(VIEW_TYPE_BROWSE, (leaf) => new BrowseView(leaf, this)); + this.addCommand({ + id: "publish-resource", + name: "Publish Resource", + callback: () => new PublishModal(this.app, this).open() + }); + this.addCommand({ + id: "update-resource", + name: "Update Resource", + callback: () => new UpdateModal(this.app, this).open() + }); + this.addCommand({ + id: "browse-resources", + name: "Browse Resources", + callback: () => this.activateBrowseView() + }); + this.addSettingTab(new VaultHubSettingTab(this.app, this)); + this.addRibbonIcon("globe", "Vault Hub", () => { + this.activateBrowseView(); + }); + } + onunload() { + } + async loadSettings() { + this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); + } + async saveSettings() { + await this.saveData(this.settings); + } + async activateBrowseView() { + const { workspace } = this.app; + let leaf = workspace.getLeavesOfType(VIEW_TYPE_BROWSE)[0]; + if (!leaf) { + const rightLeaf = workspace.getRightLeaf(false); + if (rightLeaf) { + leaf = rightLeaf; + await leaf.setViewState({ type: VIEW_TYPE_BROWSE, active: true }); + } + } + if (leaf) { + workspace.revealLeaf(leaf); + } + } +}; diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..0272449 --- /dev/null +++ b/manifest.json @@ -0,0 +1,9 @@ +{ + "id": "vault-hub", + "name": "Vault Hub", + "version": "1.0.0", + "minAppVersion": "1.0.0", + "description": "Publish CSS snippets, notes, and templates to the Vault Hub community platform.", + "author": "Maws7140", + "isDesktopOnly": false +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..1528c99 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2443 @@ +{ + "name": "vault-hub-plugin", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vault-hub-plugin", + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "@types/node": "^20.11.0", + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "builtin-modules": "^3.3.0", + "esbuild": "^0.19.11", + "obsidian": "latest", + "tslib": "^2.6.2", + "typescript": "^5.3.3" + } + }, + "node_modules/@codemirror/state": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.0.tgz", + "integrity": "sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.38.6", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.6.tgz", + "integrity": "sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@codemirror/state": "^6.5.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/codemirror": { + "version": "5.60.8", + "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz", + "integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/tern": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", + "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/tern": { + "version": "0.23.9", + "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz", + "integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0", + "peer": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/obsidian": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.12.3.tgz", + "integrity": "sha512-HxWqe763dOqzXjnNiHmAJTRERN8KILBSqxDSEqbeSr7W8R8Jxezzbca+nz1LiiqXnMpM8lV2jzAezw3CZ4xNUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/codemirror": "5.60.8", + "moment": "2.29.4" + }, + "peerDependencies": { + "@codemirror/state": "6.5.0", + "@codemirror/view": "6.38.6" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-mod": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ffe07fc --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "vault-hub-plugin", + "version": "1.0.0", + "description": "Obsidian plugin for Vault Hub community platform", + "main": "main.js", + "scripts": { + "dev": "node esbuild.config.mjs", + "build": "node esbuild.config.mjs production" + }, + "keywords": [], + "author": "Maws7140", + "license": "MIT", + "devDependencies": { + "@types/node": "^20.11.0", + "esbuild": "^0.19.11", + "obsidian": "latest", + "typescript": "^5.3.3", + "tslib": "^2.6.2", + "@typescript-eslint/parser": "^6.0.0", + "@typescript-eslint/eslint-plugin": "^6.0.0", + "builtin-modules": "^3.3.0" + } +} diff --git a/src/detection.ts b/src/detection.ts new file mode 100644 index 0000000..e949afb --- /dev/null +++ b/src/detection.ts @@ -0,0 +1,94 @@ +import { Vault } from "obsidian"; + +export interface DetectedPlugin { + id: string; + name: string; + version: string; + author: string; + autoDetected: boolean; +} + +interface PluginManifest { + id: string; + name: string; + version: string; + author: string; +} + +const MD_PATTERNS: [RegExp, string][] = [ + [/```dataviewjs/m, "dataview"], + [/```dataview/m, "dataview"], + [/```datacore/m, "datacore"], + [/```tasks/m, "obsidian-tasks-plugin"], + [/```kanban/m, "obsidian-kanban"], + [/```excalidraw/m, "obsidian-excalidraw-plugin"], + [/```chart/m, "obsidian-charts"], + [/```cards-timeline/m, "any-block"], + [/```button/m, "buttons"], + [/```meta-bind/m, "obsidian-meta-bind-plugin"], + [/`=\s*this\./m, "dataview"], + [/`=\s*dv\./m, "dataview"], + [/`\$=\s*dv\./m, "dataview"], + [/<%\s*tp\./m, "templater-obsidian"], + [/<%[-_*]?\s/m, "templater-obsidian"], + [/kanban-plugin:/m, "obsidian-kanban"], + [/^banner:/m, "obsidian-banners"], +]; + +const CSS_PATTERNS: [RegExp, string][] = [ + [/\.dataview/i, "dataview"], + [/\.kanban/i, "obsidian-kanban"], + [/\.tasks-plugin/i, "obsidian-tasks-plugin"], + [/\.excalidraw/i, "obsidian-excalidraw-plugin"], + [/\.cm-table-widget/i, "table-editor-obsidian"], +]; + +export async function getInstalledPlugins( + vault: Vault +): Promise { + const plugins: DetectedPlugin[] = []; + const pluginsDir = `${vault.configDir}/plugins`; + + const listing = await vault.adapter.list(pluginsDir); + for (const folder of listing.folders) { + const manifestPath = `${folder}/manifest.json`; + try { + const raw = await vault.adapter.read(manifestPath); + const manifest: PluginManifest = JSON.parse(raw); + plugins.push({ + id: manifest.id, + name: manifest.name, + version: manifest.version, + author: manifest.author, + autoDetected: false, + }); + } catch { + // skip plugins without valid manifest + } + } + + return plugins; +} + +export async function detectPlugins( + fileContent: string, + fileType: "css" | "md", + vault: Vault +): Promise { + const installed = await getInstalledPlugins(vault); + const installedIds = new Set(installed.map((p) => p.id)); + const detectedIds = new Set(); + + const patterns = fileType === "css" ? CSS_PATTERNS : MD_PATTERNS; + + for (const [regex, pluginId] of patterns) { + if (regex.test(fileContent) && installedIds.has(pluginId)) { + detectedIds.add(pluginId); + } + } + + return installed.map((p) => ({ + ...p, + autoDetected: detectedIds.has(p.id), + })); +} diff --git a/src/github.ts b/src/github.ts new file mode 100644 index 0000000..626bf3d --- /dev/null +++ b/src/github.ts @@ -0,0 +1,90 @@ +export class GitHubAPI { + constructor(private token: string) {} + + private async request(path: string, options: RequestInit = {}) { + const res = await fetch(`https://api.github.com${path}`, { + ...options, + headers: { + Authorization: `token ${this.token}`, + Accept: "application/vnd.github.v3+json", + "Content-Type": "application/json", + ...options.headers, + }, + }); + if (!res.ok) { + const body = await res.text(); + throw new Error(`GitHub API ${res.status}: ${body}`); + } + return res.json(); + } + + async getUser(): Promise<{ login: string; avatar_url: string }> { + return this.request("/user"); + } + + async createRepo( + name: string, + description: string + ): Promise<{ full_name: string; html_url: string }> { + return this.request("/user/repos", { + method: "POST", + body: JSON.stringify({ + name, + description, + auto_init: false, + private: false, + }), + }); + } + + async addTopics(owner: string, repo: string, topics: string[]) { + return this.request(`/repos/${owner}/${repo}/topics`, { + method: "PUT", + body: JSON.stringify({ names: topics }), + }); + } + + async createFile( + owner: string, + repo: string, + path: string, + content: string, + message: string + ) { + const encoded = btoa(unescape(encodeURIComponent(content))); + return this.request(`/repos/${owner}/${repo}/contents/${path}`, { + method: "PUT", + body: JSON.stringify({ message, content: encoded }), + }); + } + + async updateFile( + owner: string, + repo: string, + path: string, + content: string, + message: string, + sha: string + ) { + const encoded = btoa(unescape(encodeURIComponent(content))); + return this.request(`/repos/${owner}/${repo}/contents/${path}`, { + method: "PUT", + body: JSON.stringify({ message, content: encoded, sha }), + }); + } + + async getFileContent( + owner: string, + repo: string, + path: string + ): Promise<{ sha: string; content: string } | null> { + try { + const data = await this.request( + `/repos/${owner}/${repo}/contents/${path}` + ); + return { sha: data.sha, content: atob(data.content) }; + } catch { + return null; + } + } +} diff --git a/src/hubyml.ts b/src/hubyml.ts new file mode 100644 index 0000000..3312b0d --- /dev/null +++ b/src/hubyml.ts @@ -0,0 +1,75 @@ +import { DetectedPlugin } from "./detection"; + +export interface HubYmlData { + type: "snippet" | "note" | "bundle"; + name: string; + tagline: string; + description: string; + author: string; + categories: string[]; + tags: string[]; + compatibleThemes: string[]; + screenshots: string[]; + plugins: DetectedPlugin[]; + obsidianVersion: string; + theme: string; + os: string; + files: { path: string; type: string; size: number }[]; +} + +export function generateHubYml(data: HubYmlData): string { + const lines: string[] = []; + lines.push("schema: 1"); + lines.push(`type: ${data.type}`); + lines.push(`name: "${esc(data.name)}"`); + lines.push(`tagline: "${esc(data.tagline)}"`); + lines.push(`description: |`); + data.description.split("\n").forEach((l) => lines.push(` ${l}`)); + lines.push(`author: ${data.author}`); + + lines.push("categories:"); + data.categories.forEach((c) => lines.push(` - ${c}`)); + + if (data.tags.length > 0) { + lines.push("tags:"); + data.tags.forEach((t) => lines.push(` - ${t}`)); + } + + if (data.compatibleThemes.length > 0) { + lines.push("compatible_themes:"); + data.compatibleThemes.forEach((t) => lines.push(` - ${t}`)); + } + + if (data.screenshots.length > 0) { + lines.push("screenshots:"); + data.screenshots.forEach((s) => lines.push(` - ${s}`)); + } + + const selected = data.plugins.filter((p) => p.autoDetected); + if (selected.length > 0) { + lines.push("plugins:"); + selected.forEach((p) => { + lines.push(` - id: ${p.id}`); + lines.push(` name: ${p.name}`); + lines.push(` version: ${p.version}`); + }); + } + + lines.push("environment:"); + lines.push(` obsidian_version: "${data.obsidianVersion}"`); + lines.push(` theme: ${data.theme}`); + lines.push(` os: ${data.os}`); + + lines.push("files:"); + data.files.forEach((f) => { + lines.push(` - path: ${f.path}`); + lines.push(` type: ${f.type}`); + lines.push(` size: ${f.size}`); + }); + + return lines.join("\n") + "\n"; +} + +function esc(s: string): string { + return s.replace(/"/g, '\\"'); +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..1335fbb --- /dev/null +++ b/src/main.ts @@ -0,0 +1,68 @@ +import { Plugin } from "obsidian"; +import { + VaultHubSettings, + DEFAULT_SETTINGS, + VaultHubSettingTab, +} from "./settings"; +import { PublishModal } from "./modals/PublishModal"; +import { UpdateModal } from "./modals/UpdateModal"; +import { BrowseView, VIEW_TYPE_BROWSE } from "./views/BrowseView"; + +export default class VaultHubPlugin extends Plugin { + settings: VaultHubSettings = DEFAULT_SETTINGS; + + async onload() { + await this.loadSettings(); + + this.registerView(VIEW_TYPE_BROWSE, (leaf) => new BrowseView(leaf, this)); + + this.addCommand({ + id: "publish-resource", + name: "Publish Resource", + callback: () => new PublishModal(this.app, this).open(), + }); + + this.addCommand({ + id: "update-resource", + name: "Update Resource", + callback: () => new UpdateModal(this.app, this).open(), + }); + + this.addCommand({ + id: "browse-resources", + name: "Browse Resources", + callback: () => this.activateBrowseView(), + }); + + this.addSettingTab(new VaultHubSettingTab(this.app, this)); + + this.addRibbonIcon("globe", "Vault Hub", () => { + this.activateBrowseView(); + }); + } + + onunload() {} + + async loadSettings() { + this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); + } + + async saveSettings() { + await this.saveData(this.settings); + } + + private async activateBrowseView() { + const { workspace } = this.app; + let leaf = workspace.getLeavesOfType(VIEW_TYPE_BROWSE)[0]; + if (!leaf) { + const rightLeaf = workspace.getRightLeaf(false); + if (rightLeaf) { + leaf = rightLeaf; + await leaf.setViewState({ type: VIEW_TYPE_BROWSE, active: true }); + } + } + if (leaf) { + workspace.revealLeaf(leaf); + } + } +} diff --git a/src/modals/PublishModal.ts b/src/modals/PublishModal.ts new file mode 100644 index 0000000..ca1561c --- /dev/null +++ b/src/modals/PublishModal.ts @@ -0,0 +1,444 @@ +import { + App, + Modal, + Setting, + Notice, + TFile, + TextAreaComponent, + DropdownComponent, +} from "obsidian"; +import type VaultHubPlugin from "../main"; +import { DetectedPlugin, detectPlugins, getInstalledPlugins } from "../detection"; +import { GitHubAPI } from "../github"; +import { generateHubYml, HubYmlData } from "../hubyml"; +import { generateReadme, ReadmeData } from "../readme"; + +type ResourceType = "snippet" | "note" | "bundle"; + +const CATEGORIES: Record = { + snippet: [ + "ui-tweak", "theme-override", "layout", "typography", + "color-scheme", "editor", "sidebar", "publishing", + ], + note: [ + "dashboard", "tracker", "query", "daily-note", + "project-template", "kanban", "database", "automation", + ], + bundle: [ + "dashboard", "tracker", "query", "project-template", "automation", + ], +}; + +export class PublishModal extends Modal { + plugin: VaultHubPlugin; + step = 1; + + // Step 1 + resourceType: ResourceType = "snippet"; + selectedFiles: TFile[] = []; + + // Step 2 + allPlugins: DetectedPlugin[] = []; + checkedPlugins: Set = new Set(); + + // Step 3 + name = ""; + tagline = ""; + description = ""; + categories: string[] = []; + tags = ""; + compatibleThemes: string[] = []; + + // Step 4 + readmeContent = ""; + + constructor(app: App, plugin: VaultHubPlugin) { + super(app); + this.plugin = plugin; + } + + onOpen() { + this.renderStep(); + } + + onClose() { + this.contentEl.empty(); + } + + private renderStep() { + this.contentEl.empty(); + this.contentEl.addClass("vault-hub-modal"); + + const header = this.contentEl.createDiv("vault-hub-header"); + header.createEl("h2", { text: `Publish Resource — Step ${this.step} of 5` }); + const progress = header.createDiv("vault-hub-progress"); + for (let i = 1; i <= 5; i++) { + const dot = progress.createSpan("vault-hub-dot"); + if (i === this.step) dot.addClass("active"); + else if (i < this.step) dot.addClass("done"); + } + + switch (this.step) { + case 1: this.renderStep1(); break; + case 2: this.renderStep2(); break; + case 3: this.renderStep3(); break; + case 4: this.renderStep4(); break; + case 5: this.renderStep5(); break; + } + } + + private renderStep1() { + const c = this.contentEl; + + new Setting(c) + .setName("Resource Type") + .addDropdown((dd: DropdownComponent) => { + dd.addOption("snippet", "CSS Snippet"); + dd.addOption("note", "Note / Template"); + dd.addOption("bundle", "Bundle (multiple files)"); + dd.setValue(this.resourceType); + dd.onChange((v: string) => { + this.resourceType = v as ResourceType; + this.selectedFiles = []; + this.renderStep(); + }); + }); + + const ext = this.resourceType === "snippet" ? ".css" : ".md"; + const files = this.app.vault + .getFiles() + .filter((f: TFile) => f.extension === ext.slice(1)) + .sort((a: TFile, b: TFile) => a.path.localeCompare(b.path)); + + const fileSection = c.createDiv(); + fileSection.createEl("h4", { text: `Select file${this.resourceType === "bundle" ? "s" : ""}` }); + + const list = fileSection.createDiv("vault-hub-file-list"); + files.forEach((f: TFile) => { + const row = list.createDiv("vault-hub-file-row"); + const cb = row.createEl("input", { type: this.resourceType === "bundle" ? "checkbox" : "radio" }); + cb.name = "vault-hub-file"; + cb.checked = this.selectedFiles.includes(f); + cb.addEventListener("change", () => { + if (this.resourceType === "bundle") { + if (cb.checked) this.selectedFiles.push(f); + else this.selectedFiles = this.selectedFiles.filter((x) => x !== f); + } else { + this.selectedFiles = cb.checked ? [f] : []; + } + }); + row.createSpan({ text: f.path }); + }); + + this.addNav(c, null, () => { + if (this.selectedFiles.length === 0) { + new Notice("Select at least one file"); + return false; + } + return true; + }); + } + + private async renderStep2() { + const c = this.contentEl; + c.createEl("h4", { text: "Select Required Plugins" }); + c.createEl("p", { + text: "Auto-detected plugins are pre-checked. Review and adjust.", + cls: "vault-hub-hint", + }); + + const loading = c.createDiv({ text: "Scanning..." }); + + const content = await this.app.vault.read(this.selectedFiles[0]); + const fileType = this.resourceType === "snippet" ? "css" : "md"; + this.allPlugins = await detectPlugins(content, fileType as "css" | "md", this.app.vault); + + loading.remove(); + + // Pre-check auto-detected + this.allPlugins.forEach((p) => { + if (p.autoDetected) this.checkedPlugins.add(p.id); + }); + + const list = c.createDiv("vault-hub-plugin-list"); + this.allPlugins.forEach((p) => { + const row = list.createDiv("vault-hub-plugin-row"); + const cb = row.createEl("input", { type: "checkbox" }); + cb.checked = this.checkedPlugins.has(p.id); + cb.addEventListener("change", () => { + if (cb.checked) this.checkedPlugins.add(p.id); + else this.checkedPlugins.delete(p.id); + }); + + const info = row.createDiv("vault-hub-plugin-info"); + info.createSpan({ text: p.name, cls: "vault-hub-plugin-name" }); + info.createSpan({ text: ` v${p.version}`, cls: "vault-hub-plugin-version" }); + if (p.autoDetected) { + info.createSpan({ text: " (auto-detected)", cls: "vault-hub-auto-badge" }); + } + }); + + if (this.allPlugins.length === 0) { + c.createEl("p", { text: "No community plugins installed.", cls: "vault-hub-hint" }); + } + + this.addNav(c, null, null); + } + + private renderStep3() { + const c = this.contentEl; + + new Setting(c).setName("Name").addText((t) => { + t.setPlaceholder("My Resource").setValue(this.name); + t.onChange((v: string) => (this.name = v)); + t.inputEl.style.width = "100%"; + }); + + new Setting(c).setName("Tagline").setDesc("One-line summary").addText((t) => { + t.setPlaceholder("A brief description").setValue(this.tagline); + t.onChange((v: string) => (this.tagline = v)); + t.inputEl.style.width = "100%"; + }); + + new Setting(c).setName("Description").addTextArea((t: TextAreaComponent) => { + t.setPlaceholder("Detailed description...").setValue(this.description); + t.onChange((v: string) => (this.description = v)); + t.inputEl.style.width = "100%"; + t.inputEl.style.minHeight = "80px"; + }); + + const cats = CATEGORIES[this.resourceType] || []; + new Setting(c).setName("Category").addDropdown((dd: DropdownComponent) => { + dd.addOption("", "Select..."); + cats.forEach((cat) => dd.addOption(cat, cat)); + dd.setValue(this.categories[0] || ""); + dd.onChange((v: string) => (this.categories = v ? [v] : [])); + }); + + new Setting(c).setName("Tags").setDesc("Comma-separated").addText((t) => { + t.setPlaceholder("glass, blur, dark").setValue(this.tags); + t.onChange((v: string) => (this.tags = v)); + }); + + if (this.resourceType === "snippet") { + new Setting(c).setName("Compatible Themes").addDropdown((dd: DropdownComponent) => { + dd.addOption("any", "Any theme"); + ["minimal", "velocity", "obsidian-default", "catppuccin"].forEach((t) => + dd.addOption(t, t) + ); + dd.setValue(this.compatibleThemes[0] || "any"); + dd.onChange((v: string) => (this.compatibleThemes = [v])); + }); + } + + this.addNav(c, null, () => { + if (!this.name.trim()) { + new Notice("Name is required"); + return false; + } + // Generate README for next step + const selected = this.allPlugins + .filter((p) => this.checkedPlugins.has(p.id)) + .map((p) => ({ ...p, autoDetected: true })); + + const readmeData: ReadmeData = { + name: this.name, + tagline: this.tagline, + description: this.description, + type: this.resourceType, + plugins: selected, + files: this.selectedFiles.map((f) => ({ path: f.name })), + }; + this.readmeContent = generateReadme(readmeData); + return true; + }); + } + + private renderStep4() { + const c = this.contentEl; + c.createEl("h4", { text: "Edit README" }); + c.createEl("p", { + text: "Auto-generated from your details. Edit freely.", + cls: "vault-hub-hint", + }); + + const textarea = c.createEl("textarea", { cls: "vault-hub-readme-editor" }); + textarea.value = this.readmeContent; + textarea.addEventListener("input", () => { + this.readmeContent = textarea.value; + }); + + this.addNav(c, null, null); + } + + private renderStep5() { + const c = this.contentEl; + c.createEl("h4", { text: "Review & Publish" }); + + const summary = c.createDiv("vault-hub-summary"); + summary.createEl("p", { text: `Type: ${this.resourceType}` }); + summary.createEl("p", { text: `Name: ${this.name}` }); + summary.createEl("p", { text: `Files: ${this.selectedFiles.map((f) => f.name).join(", ")}` }); + + const selPlugins = this.allPlugins.filter((p) => this.checkedPlugins.has(p.id)); + summary.createEl("p", { + text: `Plugins: ${selPlugins.length > 0 ? selPlugins.map((p) => p.name).join(", ") : "None"}`, + }); + summary.createEl("p", { text: `Categories: ${this.categories.join(", ") || "None"}` }); + + const btnContainer = c.createDiv("vault-hub-nav"); + + const backBtn = btnContainer.createEl("button", { text: "Back" }); + backBtn.addEventListener("click", () => { this.step--; this.renderStep(); }); + + const publishBtn = btnContainer.createEl("button", { + text: "Publish", + cls: "mod-cta", + }); + publishBtn.addEventListener("click", () => this.doPublish()); + } + + private async doPublish() { + const token = this.plugin.settings.githubToken; + if (!token) { + new Notice("Set your GitHub token in Vault Hub settings first"); + return; + } + + const c = this.contentEl; + c.empty(); + c.createEl("h3", { text: "Publishing..." }); + const status = c.createEl("p", { text: "Creating repository..." }); + + try { + const gh = new GitHubAPI(token); + const user = await gh.getUser(); + + const slug = this.name + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-|-$/g, ""); + const repoName = `obsidian-${this.resourceType}-${slug}`; + + status.setText("Creating repository..."); + const repo = await gh.createRepo(repoName, this.tagline || this.name); + const [owner, rName] = repo.full_name.split("/"); + + // Topic tag + const topicMap: Record = { + snippet: "obsidian-css-snippet", + note: "obsidian-note-template", + bundle: "obsidian-note-template", + }; + status.setText("Adding topic tag..."); + await gh.addTopics(owner, rName, [topicMap[this.resourceType]]); + + // Upload resource files + for (const file of this.selectedFiles) { + status.setText(`Uploading ${file.name}...`); + const content = await this.app.vault.read(file); + await gh.createFile(owner, rName, file.name, content, `Add ${file.name}`); + } + + // Generate and upload hub.yml + status.setText("Generating hub.yml..."); + const selectedPlugins = this.allPlugins + .filter((p) => this.checkedPlugins.has(p.id)) + .map((p) => ({ ...p, autoDetected: true })); + + const obsVer = (this.app as unknown as { appVersion?: string }).appVersion || "unknown"; + const themeName = ((this.app.vault as unknown as { config?: { cssTheme?: string } }).config?.cssTheme) || "default"; + + const hubData: HubYmlData = { + type: this.resourceType, + name: this.name, + tagline: this.tagline, + description: this.description, + author: user.login, + categories: this.categories, + tags: this.tags.split(",").map((t) => t.trim()).filter(Boolean), + compatibleThemes: this.compatibleThemes, + screenshots: [], + plugins: selectedPlugins, + obsidianVersion: obsVer, + theme: themeName, + os: navigator.platform, + files: this.selectedFiles.map((f) => ({ + path: f.name, + type: f.extension, + size: f.stat.size, + })), + }; + + const hubYml = generateHubYml(hubData); + await gh.createFile(owner, rName, "hub.yml", hubYml, "Add hub.yml"); + + // Upload README + status.setText("Uploading README..."); + await gh.createFile(owner, rName, "README.md", this.readmeContent, "Add README"); + + // Save to published resources + this.plugin.settings.publishedResources.push({ + repoFullName: repo.full_name, + localFilePath: this.selectedFiles[0].path, + type: this.resourceType, + lastPublishedAt: new Date().toISOString(), + }); + await this.plugin.saveSettings(); + + // Success + c.empty(); + c.createEl("h3", { text: "Published!" }); + c.createEl("p", { text: `Repository: ${repo.full_name}` }); + const link = c.createEl("a", { + text: repo.html_url, + href: repo.html_url, + }); + link.setAttr("target", "_blank"); + c.createEl("p", { + text: "It will appear on Vault Hub within 6 hours.", + cls: "vault-hub-hint", + }); + + const closeBtn = c.createEl("button", { text: "Close", cls: "mod-cta" }); + closeBtn.addEventListener("click", () => this.close()); + + new Notice(`Published to ${repo.full_name}!`); + } catch (e) { + c.empty(); + c.createEl("h3", { text: "Error" }); + c.createEl("p", { text: String(e) }); + const retryBtn = c.createEl("button", { text: "Back to Review" }); + retryBtn.addEventListener("click", () => { + this.step = 5; + this.renderStep(); + }); + } + } + + private addNav( + container: HTMLElement, + backCheck: (() => boolean) | null, + nextCheck: (() => boolean) | null + ) { + const nav = container.createDiv("vault-hub-nav"); + + if (this.step > 1) { + const back = nav.createEl("button", { text: "Back" }); + back.addEventListener("click", () => { + if (backCheck && !backCheck()) return; + this.step--; + this.renderStep(); + }); + } + + if (this.step < 5) { + const next = nav.createEl("button", { text: "Next", cls: "mod-cta" }); + next.addEventListener("click", () => { + if (nextCheck && !nextCheck()) return; + this.step++; + this.renderStep(); + }); + } + } +} diff --git a/src/modals/UpdateModal.ts b/src/modals/UpdateModal.ts new file mode 100644 index 0000000..780c25d --- /dev/null +++ b/src/modals/UpdateModal.ts @@ -0,0 +1,107 @@ +import { App, Modal, Setting, Notice } from "obsidian"; +import type VaultHubPlugin from "../main"; +import { PublishedResource } from "../settings"; +import { GitHubAPI } from "../github"; + +export class UpdateModal extends Modal { + plugin: VaultHubPlugin; + selected: PublishedResource | null = null; + + constructor(app: App, plugin: VaultHubPlugin) { + super(app); + this.plugin = plugin; + } + + onOpen() { + this.render(); + } + + onClose() { + this.contentEl.empty(); + } + + private render() { + const c = this.contentEl; + c.empty(); + c.createEl("h2", { text: "Update Published Resource" }); + + const resources = this.plugin.settings.publishedResources; + if (resources.length === 0) { + c.createEl("p", { text: "No published resources yet. Use Publish first." }); + return; + } + + new Setting(c).setName("Select Resource").addDropdown((dd) => { + dd.addOption("", "Choose..."); + resources.forEach((r, i) => { + dd.addOption(String(i), `${r.repoFullName} (${r.type})`); + }); + dd.onChange((v) => { + this.selected = v ? resources[parseInt(v)] : null; + }); + }); + + const updateBtn = c.createEl("button", { text: "Push Update", cls: "mod-cta" }); + updateBtn.addEventListener("click", () => this.doUpdate()); + } + + private async doUpdate() { + if (!this.selected) { + new Notice("Select a resource first"); + return; + } + + const token = this.plugin.settings.githubToken; + if (!token) { + new Notice("Set your GitHub token in settings first"); + return; + } + + const c = this.contentEl; + c.empty(); + c.createEl("h3", { text: "Updating..." }); + + try { + const gh = new GitHubAPI(token); + const [owner, repo] = this.selected.repoFullName.split("/"); + + // Read current local file + const file = this.app.vault.getAbstractFileByPath(this.selected.localFilePath); + if (!file) { + new Notice(`File not found: ${this.selected.localFilePath}`); + return; + } + + const content = await this.app.vault.read(file as import("obsidian").TFile); + + // Get existing file SHA from GitHub + const existing = await gh.getFileContent(owner, repo, file.name); + + if (existing) { + await gh.updateFile( + owner, repo, file.name, content, + `Update ${file.name}`, existing.sha + ); + } else { + await gh.createFile(owner, repo, file.name, content, `Add ${file.name}`); + } + + // Update timestamp + this.selected.lastPublishedAt = new Date().toISOString(); + await this.plugin.saveSettings(); + + c.empty(); + c.createEl("h3", { text: "Updated!" }); + c.createEl("p", { text: `Pushed to ${this.selected.repoFullName}` }); + + const closeBtn = c.createEl("button", { text: "Close", cls: "mod-cta" }); + closeBtn.addEventListener("click", () => this.close()); + + new Notice("Resource updated!"); + } catch (e) { + c.empty(); + c.createEl("h3", { text: "Error" }); + c.createEl("p", { text: String(e) }); + } + } +} diff --git a/src/readme.ts b/src/readme.ts new file mode 100644 index 0000000..4ec5c15 --- /dev/null +++ b/src/readme.ts @@ -0,0 +1,63 @@ +import { DetectedPlugin } from "./detection"; + +export interface ReadmeData { + name: string; + tagline: string; + description: string; + type: "snippet" | "note" | "bundle"; + plugins: DetectedPlugin[]; + files: { path: string }[]; +} + +export function generateReadme(data: ReadmeData): string { + const lines: string[] = []; + + lines.push(`# ${data.name}`); + lines.push(""); + lines.push(data.tagline); + lines.push(""); + lines.push("## Description"); + lines.push(""); + lines.push(data.description); + lines.push(""); + + const selected = data.plugins.filter((p) => p.autoDetected); + if (selected.length > 0) { + lines.push("## Required Plugins"); + lines.push(""); + lines.push("| Plugin | Version | Link |"); + lines.push("|--------|---------|------|"); + selected.forEach((p) => { + lines.push( + `| ${p.name} | ${p.version} | [GitHub](https://github.com/search?q=${encodeURIComponent(p.name + " obsidian")}) |` + ); + }); + lines.push(""); + } + + lines.push("## Installation"); + lines.push(""); + + if (data.type === "snippet") { + lines.push( + `1. Download \`${data.files[0]?.path || "snippet.css"}\` from this repo` + ); + lines.push( + "2. Place it in your vault's `.obsidian/snippets/` folder" + ); + lines.push("3. Enable it in Settings > Appearance > CSS Snippets"); + } else { + lines.push("1. Download the `.md` file(s) from this repo"); + lines.push("2. Place them in your vault"); + if (selected.length > 0) { + lines.push("3. Install the required plugins listed above"); + } + } + + lines.push(""); + lines.push("---"); + lines.push("*Published via [Vault Hub](https://vaulthub.dev)*"); + lines.push(""); + + return lines.join("\n"); +} diff --git a/src/settings.ts b/src/settings.ts new file mode 100644 index 0000000..e05d54a --- /dev/null +++ b/src/settings.ts @@ -0,0 +1,85 @@ +import { App, PluginSettingTab, Setting } from "obsidian"; +import type VaultHubPlugin from "./main"; + +export interface PublishedResource { + repoFullName: string; + localFilePath: string; + type: "snippet" | "note" | "bundle"; + lastPublishedAt: string; +} + +export interface VaultHubSettings { + githubToken: string; + defaultCategories: string[]; + vaultHubUrl: string; + publishedResources: PublishedResource[]; +} + +export const DEFAULT_SETTINGS: VaultHubSettings = { + githubToken: "", + defaultCategories: [], + vaultHubUrl: "http://localhost:3000", + publishedResources: [], +}; + +export class VaultHubSettingTab extends PluginSettingTab { + plugin: VaultHubPlugin; + + constructor(app: App, plugin: VaultHubPlugin) { + super(app, plugin); + this.plugin = plugin; + } + + display(): void { + const { containerEl } = this; + containerEl.empty(); + + containerEl.createEl("h2", { text: "Vault Hub Settings" }); + + new Setting(containerEl) + .setName("GitHub Personal Access Token") + .setDesc("Token used to create repos and push files. Requires 'repo' scope.") + .addText((text) => + text + .setPlaceholder("ghp_xxxxxxxxxxxx") + .setValue(this.plugin.settings.githubToken) + .then((t) => { + t.inputEl.type = "password"; + t.inputEl.style.width = "300px"; + }) + .onChange(async (value) => { + this.plugin.settings.githubToken = value; + await this.plugin.saveSettings(); + }) + ); + + new Setting(containerEl) + .setName("Vault Hub URL") + .setDesc("URL of the Vault Hub website (default: http://localhost:3000 for development).") + .addText((text) => + text + .setPlaceholder("http://localhost:3000") + .setValue(this.plugin.settings.vaultHubUrl) + .onChange(async (value) => { + this.plugin.settings.vaultHubUrl = value; + await this.plugin.saveSettings(); + }) + ); + + new Setting(containerEl) + .setName("Default Categories") + .setDesc("Comma-separated list of default categories for new publications.") + .addText((text) => + text + .setPlaceholder("appearance, workflow") + .setValue(this.plugin.settings.defaultCategories.join(", ")) + .onChange(async (value) => { + this.plugin.settings.defaultCategories = value + .split(",") + .map((s) => s.trim()) + .filter((s) => s.length > 0); + await this.plugin.saveSettings(); + }) + ); + } +} diff --git a/src/views/BrowseView.ts b/src/views/BrowseView.ts new file mode 100644 index 0000000..c2de4e6 --- /dev/null +++ b/src/views/BrowseView.ts @@ -0,0 +1,193 @@ +import { ItemView, WorkspaceLeaf, Notice, TFile } from "obsidian"; +import type VaultHubPlugin from "../main"; + +export const VIEW_TYPE_BROWSE = "vault-hub-browse"; + +const SUPABASE_URL = "https://oxvxiqiushhpwzqtpzdg.supabase.co"; +const SUPABASE_KEY = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im94dnhpcWl1c2hocHd6cXRwemRnIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzU0MTI4NjAsImV4cCI6MjA5MDk4ODg2MH0.jOJloJvJmV2Q-xuY1wBbUjg0s7DnRGt6htycj4HBtEA"; + +interface ResourceSummary { + id: string; + type: string; + title: string; + owner: string; + repo_name: string; + full_name: string; + tagline: string | null; + stars: number; + vote_count: number; +} + +export class BrowseView extends ItemView { + plugin: VaultHubPlugin; + searchQuery = ""; + resources: ResourceSummary[] = []; + + constructor(leaf: WorkspaceLeaf, plugin: VaultHubPlugin) { + super(leaf); + this.plugin = plugin; + } + + getViewType(): string { + return VIEW_TYPE_BROWSE; + } + + getDisplayText(): string { + return "Vault Hub"; + } + + getIcon(): string { + return "globe"; + } + + async onOpen() { + this.render(); + await this.loadResources(); + } + + async onClose() { + this.contentEl.empty(); + } + + private render() { + const c = this.contentEl; + c.empty(); + c.addClass("vault-hub-browse"); + + const header = c.createDiv("vault-hub-browse-header"); + header.createEl("h3", { text: "Vault Hub" }); + + const searchRow = header.createDiv("vault-hub-search-row"); + const input = searchRow.createEl("input", { + type: "text", + placeholder: "Search resources...", + }); + input.value = this.searchQuery; + input.addEventListener("input", () => { + this.searchQuery = input.value; + }); + input.addEventListener("keydown", (e: KeyboardEvent) => { + if (e.key === "Enter") this.loadResources(); + }); + + const searchBtn = searchRow.createEl("button", { text: "Search" }); + searchBtn.addEventListener("click", () => this.loadResources()); + + c.createDiv("vault-hub-results"); + } + + private async loadResources() { + const resultsEl = this.contentEl.querySelector(".vault-hub-results"); + if (!resultsEl) return; + + resultsEl.empty(); + resultsEl.createEl("p", { text: "Loading...", cls: "vault-hub-hint" }); + + try { + let url = `${SUPABASE_URL}/rest/v1/resources?select=id,type,title,owner,repo_name,full_name,tagline,stars,vote_count&is_active=eq.true&order=trending_score.desc&limit=30`; + + if (this.searchQuery.trim()) { + url += `&or=(title.ilike.*${encodeURIComponent(this.searchQuery)}*,description.ilike.*${encodeURIComponent(this.searchQuery)}*)`; + } + + const res = await fetch(url, { + headers: { + apikey: SUPABASE_KEY, + Authorization: `Bearer ${SUPABASE_KEY}`, + }, + }); + + this.resources = await res.json(); + this.renderResults(resultsEl as HTMLElement); + } catch (e) { + resultsEl.empty(); + resultsEl.createEl("p", { text: `Error: ${e}` }); + } + } + + private renderResults(container: HTMLElement) { + container.empty(); + + if (this.resources.length === 0) { + container.createEl("p", { + text: "No resources found.", + cls: "vault-hub-hint", + }); + return; + } + + this.resources.forEach((r) => { + const card = container.createDiv("vault-hub-result-card"); + + const typeColors: Record = { + vault: "#7f6df2", + snippet: "#22d3ee", + note: "#fb923c", + }; + + const badge = card.createSpan("vault-hub-type-badge"); + badge.setText(r.type); + badge.style.backgroundColor = typeColors[r.type] || "#666"; + + const title = card.createEl("h4"); + title.setText(r.title); + + if (r.tagline) { + card.createEl("p", { text: r.tagline, cls: "vault-hub-hint" }); + } + + const meta = card.createDiv("vault-hub-result-meta"); + meta.createSpan({ text: `${r.stars} stars` }); + meta.createSpan({ text: `${r.vote_count} votes` }); + meta.createSpan({ text: r.owner }); + + const actions = card.createDiv("vault-hub-result-actions"); + + const installBtn = actions.createEl("button", { text: "Install" }); + installBtn.addEventListener("click", () => this.installResource(r)); + + const ghBtn = actions.createEl("button", { text: "GitHub" }); + ghBtn.addEventListener("click", () => { + window.open(`https://github.com/${r.full_name}`, "_blank"); + }); + }); + } + + private async installResource(r: ResourceSummary) { + try { + // Fetch file list from GitHub + const res = await fetch( + `https://api.github.com/repos/${r.full_name}/contents`, + { headers: { Accept: "application/vnd.github.v3+json" } } + ); + const files = await res.json(); + + if (r.type === "snippet") { + const cssFile = files.find( + (f: { name: string }) => f.name.endsWith(".css") + ); + if (cssFile) { + const raw = await fetch(cssFile.download_url); + const content = await raw.text(); + const snippetPath = `${this.app.vault.configDir}/snippets/${cssFile.name}`; + await this.app.vault.adapter.write(snippetPath, content); + new Notice(`Installed snippet: ${cssFile.name}`); + } + } else { + const mdFiles = files.filter( + (f: { name: string }) => + f.name.endsWith(".md") && f.name !== "README.md" + ); + for (const f of mdFiles) { + const raw = await fetch(f.download_url); + const content = await raw.text(); + await this.app.vault.create(f.name, content); + new Notice(`Installed: ${f.name}`); + } + } + } catch (e) { + new Notice(`Install failed: ${e}`); + } + } +} diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..95044a7 --- /dev/null +++ b/styles.css @@ -0,0 +1,230 @@ +/* Vault Hub Plugin Styles */ + +/* Browse View */ +.vault-hub-browse { + padding: 12px; +} + +.vault-hub-browse-header h3 { + margin: 0 0 12px 0; + font-size: 1.2em; +} + +.vault-hub-search-row { + display: flex; + gap: 8px; + margin-bottom: 16px; +} + +.vault-hub-search-row input[type="text"] { + flex: 1; + padding: 6px 10px; + border-radius: 6px; + border: 1px solid var(--background-modifier-border); + background: var(--background-primary); + color: var(--text-normal); + font-size: 0.9em; +} + +.vault-hub-search-row button { + padding: 6px 14px; + border-radius: 6px; + cursor: pointer; +} + +.vault-hub-hint { + color: var(--text-muted); + font-size: 0.85em; +} + +/* Result Cards */ +.vault-hub-result-card { + padding: 12px; + margin-bottom: 10px; + border-radius: 8px; + background: var(--background-secondary); + border: 1px solid var(--background-modifier-border); +} + +.vault-hub-result-card h4 { + margin: 6px 0 4px 0; + font-size: 1em; +} + +.vault-hub-type-badge { + display: inline-block; + padding: 2px 8px; + border-radius: 4px; + font-size: 0.75em; + font-weight: 600; + color: #fff; + text-transform: capitalize; +} + +.vault-hub-result-meta { + display: flex; + gap: 12px; + margin-top: 6px; + font-size: 0.8em; + color: var(--text-muted); +} + +.vault-hub-result-actions { + display: flex; + gap: 8px; + margin-top: 8px; +} + +.vault-hub-result-actions button { + padding: 4px 12px; + border-radius: 6px; + font-size: 0.85em; + cursor: pointer; +} + +/* Modal Styles */ +.vault-hub-modal { + min-width: 480px; +} + +.vault-hub-header { + margin-bottom: 16px; +} + +.vault-hub-header h2 { + margin: 0 0 8px 0; +} + +/* Progress Dots */ +.vault-hub-progress { + display: flex; + gap: 8px; + margin-bottom: 8px; +} + +.vault-hub-dot { + width: 10px; + height: 10px; + border-radius: 50%; + background: var(--background-modifier-border); + transition: background 0.2s; +} + +.vault-hub-dot.active { + background: var(--interactive-accent); + box-shadow: 0 0 6px var(--interactive-accent); +} + +.vault-hub-dot.done { + background: var(--text-success); +} + +/* File List */ +.vault-hub-file-list { + max-height: 300px; + overflow-y: auto; + border: 1px solid var(--background-modifier-border); + border-radius: 6px; + margin-top: 8px; +} + +.vault-hub-file-row { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 10px; + border-bottom: 1px solid var(--background-modifier-border); + font-size: 0.9em; +} + +.vault-hub-file-row:last-child { + border-bottom: none; +} + +.vault-hub-file-row:hover { + background: var(--background-modifier-hover); +} + +/* Plugin List */ +.vault-hub-plugin-list { + max-height: 300px; + overflow-y: auto; + margin-top: 8px; +} + +.vault-hub-plugin-row { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 10px; + border-bottom: 1px solid var(--background-modifier-border); +} + +.vault-hub-plugin-row:last-child { + border-bottom: none; +} + +.vault-hub-plugin-info { + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: 4px; +} + +.vault-hub-plugin-name { + font-weight: 600; +} + +.vault-hub-plugin-version { + color: var(--text-muted); + font-size: 0.85em; +} + +.vault-hub-auto-badge { + font-size: 0.75em; + color: var(--text-success); + font-style: italic; +} + +/* README Editor */ +.vault-hub-readme-editor { + width: 100%; + min-height: 300px; + padding: 10px; + border-radius: 6px; + border: 1px solid var(--background-modifier-border); + background: var(--background-primary); + color: var(--text-normal); + font-family: var(--font-monospace); + font-size: 0.85em; + resize: vertical; +} + +/* Summary */ +.vault-hub-summary { + padding: 12px; + border-radius: 6px; + background: var(--background-secondary); + margin-bottom: 16px; +} + +.vault-hub-summary p { + margin: 4px 0; + font-size: 0.9em; +} + +/* Navigation */ +.vault-hub-nav { + display: flex; + justify-content: flex-end; + gap: 8px; + margin-top: 16px; + padding-top: 12px; + border-top: 1px solid var(--background-modifier-border); +} + +.vault-hub-nav button { + padding: 6px 18px; + border-radius: 6px; + cursor: pointer; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..2164ff0 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "inlineSourceMap": true, + "inlineSources": true, + "module": "ESNext", + "target": "ES6", + "allowJs": true, + "noImplicitAny": true, + "moduleResolution": "node", + "importHelpers": true, + "isolatedModules": true, + "strictNullChecks": true, + "lib": ["DOM", "ES5", "ES6", "ES7", "ES2015", "ES2020"], + "outDir": "./dist", + "sourceMap": false, + "paths": { + "obsidian": ["node_modules/obsidian"] + } + }, + "include": ["src/**/*.ts"] +}