/*
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: () => HoarderPlugin
});
module.exports = __toCommonJS(main_exports);
var import_obsidian3 = require("obsidian");

// src/asset-handler.ts
function getAssetUrl(assetId, client, settings) {
  if (client) {
    return client.getAssetUrl(assetId);
  }
  const baseUrl = settings.apiEndpoint.replace(/\/v1\/?$/, "");
  return `${baseUrl}/assets/${assetId}`;
}
function sanitizeAssetFileName(title) {
  let sanitizedTitle = title.replace(/[\\/:*?"<>|]/g, "-").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
  const maxTitleLength = 30;
  if (sanitizedTitle.length > maxTitleLength) {
    const truncated = sanitizedTitle.substring(0, maxTitleLength);
    const lastDash = truncated.lastIndexOf("-");
    if (lastDash > maxTitleLength / 2) {
      sanitizedTitle = truncated.substring(0, lastDash);
    } else {
      sanitizedTitle = truncated;
    }
  }
  return sanitizedTitle;
}
async function downloadImage(app, url, assetId, title, client, settings) {
  var _a;
  try {
    if (!await app.vault.adapter.exists(settings.attachmentsFolder)) {
      await app.vault.createFolder(settings.attachmentsFolder);
    }
    const extension = ((_a = url.split(".").pop()) == null ? void 0 : _a.toLowerCase()) || "jpg";
    const safeExtension = ["jpg", "jpeg", "png", "gif", "webp"].includes(extension) ? extension : "jpg";
    const safeTitle = sanitizeAssetFileName(title);
    const fileName = `${assetId}${safeTitle ? "-" + safeTitle : ""}.${safeExtension}`;
    const filePath = `${settings.attachmentsFolder}/${fileName}`;
    const files = await app.vault.adapter.list(settings.attachmentsFolder);
    const existingFile = files.files.find(
      (filePathItem) => filePathItem.startsWith(`${settings.attachmentsFolder}/${assetId}`)
    );
    if (existingFile) {
      return existingFile;
    }
    let buffer;
    const apiDomain = new URL(settings.apiEndpoint).origin;
    if (url.startsWith(apiDomain) && client) {
      buffer = await client.downloadAsset(assetId);
    } else {
      const headers = {};
      if (url.startsWith(apiDomain)) {
        headers["Authorization"] = `Bearer ${settings.apiKey}`;
      }
      const response = await fetch(url, { headers });
      if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
      buffer = await response.arrayBuffer();
    }
    await app.vault.adapter.writeBinary(filePath, buffer);
    return filePath;
  } catch (error) {
    console.error("Error downloading image:", url, error);
    return null;
  }
}
function escapeMarkdownPath(path) {
  if (path.includes(" ") || /[<>\[\](){}]/.test(path)) {
    return `<${path}>`;
  }
  return path;
}
var toWikilink = (path) => `"[[${path}]]"`;
async function processBookmarkAssets(app, bookmark, title, client, settings) {
  let content = "";
  const fm = {};
  if (bookmark.content.type === "asset" && bookmark.content.assetType === "image") {
    if (bookmark.content.assetId) {
      const assetUrl = getAssetUrl(bookmark.content.assetId, client, settings);
      let imagePath = null;
      if (settings.downloadAssets) {
        imagePath = await downloadImage(
          app,
          assetUrl,
          bookmark.content.assetId,
          title,
          client,
          settings
        );
      }
      if (imagePath) {
        content += `
![${title}](${escapeMarkdownPath(imagePath)})
`;
        fm.image = toWikilink(imagePath);
      } else {
        content += `
![${title}](${escapeMarkdownPath(assetUrl)})
`;
      }
    } else if (bookmark.content.sourceUrl) {
      content += `
![${title}](${escapeMarkdownPath(bookmark.content.sourceUrl)})
`;
    }
  } else if (bookmark.content.type === "link") {
    const assetIds = [];
    const assetLabels = [];
    if (bookmark.content.imageAssetId) {
      assetIds.push(bookmark.content.imageAssetId);
      assetLabels.push("Banner Image");
    }
    if (bookmark.content.screenshotAssetId) {
      assetIds.push(bookmark.content.screenshotAssetId);
      assetLabels.push("Screenshot");
    }
    if (bookmark.content.fullPageArchiveAssetId) {
      assetIds.push(bookmark.content.fullPageArchiveAssetId);
      assetLabels.push("Full Page Archive");
    }
    if (bookmark.content.videoAssetId) {
      assetIds.push(bookmark.content.videoAssetId);
      assetLabels.push("Video");
    }
    for (let i = 0; i < assetIds.length; i++) {
      const assetId = assetIds[i];
      const label = assetLabels[i];
      const assetUrl = getAssetUrl(assetId, client, settings);
      if (label === "Video") {
        content += `
[${title} - ${label}](${escapeMarkdownPath(assetUrl)})
`;
      } else {
        let imagePath = null;
        if (settings.downloadAssets) {
          imagePath = await downloadImage(
            app,
            assetUrl,
            assetId,
            `${title}-${label}`,
            client,
            settings
          );
        }
        if (imagePath) {
          content += `
![${title} - ${label}](${escapeMarkdownPath(imagePath)})
`;
          if (label === "Banner Image") {
            fm.banner = toWikilink(imagePath);
          } else if (label === "Screenshot") {
            fm.screenshot = toWikilink(imagePath);
          } else if (label === "Full Page Archive") {
            fm.full_page_archive = toWikilink(imagePath);
          }
        } else {
          content += `
![${title} - ${label}](${escapeMarkdownPath(assetUrl)})
`;
        }
      }
    }
    if (assetIds.length === 0 && bookmark.content.imageUrl) {
      content += `
![${title}](${escapeMarkdownPath(bookmark.content.imageUrl)})
`;
    }
  }
  if (bookmark.assets && bookmark.assets.length > 0) {
    const processedAssetIds = /* @__PURE__ */ new Set();
    if (bookmark.content.type === "asset" && bookmark.content.assetId) {
      processedAssetIds.add(bookmark.content.assetId);
    }
    if (bookmark.content.type === "link") {
      if (bookmark.content.imageAssetId) processedAssetIds.add(bookmark.content.imageAssetId);
      if (bookmark.content.screenshotAssetId)
        processedAssetIds.add(bookmark.content.screenshotAssetId);
      if (bookmark.content.fullPageArchiveAssetId)
        processedAssetIds.add(bookmark.content.fullPageArchiveAssetId);
      if (bookmark.content.videoAssetId) processedAssetIds.add(bookmark.content.videoAssetId);
    }
    for (const asset of bookmark.assets) {
      if (!processedAssetIds.has(asset.id)) {
        const assetUrl = getAssetUrl(asset.id, client, settings);
        const label = asset.assetType === "image" ? "Additional Image" : asset.assetType;
        if (asset.assetType === "video") {
          content += `
[${title} - ${label}](${escapeMarkdownPath(assetUrl)})
`;
        } else {
          let imagePath = null;
          if (settings.downloadAssets) {
            imagePath = await downloadImage(
              app,
              assetUrl,
              asset.id,
              `${title}-${label}`,
              client,
              settings
            );
          }
          if (imagePath) {
            content += `
![${title} - ${label}](${escapeMarkdownPath(imagePath)})
`;
            fm.additional = fm.additional || [];
            fm.additional.push(toWikilink(imagePath));
          } else {
            content += `
![${title} - ${label}](${escapeMarkdownPath(assetUrl)})
`;
          }
        }
      }
    }
  }
  return { content, frontmatter: Object.keys(fm).length > 0 ? fm : null };
}

// src/bookmark-utils.ts
function getBookmarkTitle(bookmark) {
  if (bookmark.title) {
    return bookmark.title;
  }
  if (bookmark.content.type === "link") {
    if (bookmark.content.title) {
      return bookmark.content.title;
    }
    if (bookmark.content.url) {
      return extractTitleFromUrl(bookmark.content.url);
    }
  } else if (bookmark.content.type === "text") {
    if (bookmark.content.text) {
      return extractTitleFromText(bookmark.content.text);
    }
  } else if (bookmark.content.type === "asset") {
    if (bookmark.content.fileName) {
      return bookmark.content.fileName.replace(/\.[^/.]+$/, "");
    }
    if (bookmark.content.sourceUrl) {
      return extractTitleFromUrl(bookmark.content.sourceUrl);
    }
  }
  return `Bookmark-${bookmark.id}-${new Date(bookmark.createdAt).toISOString().split("T")[0]}`;
}
function extractTitleFromUrl(url) {
  var _a, _b;
  try {
    const parsedUrl = new URL(url);
    const pathTitle = (_b = (_a = parsedUrl.pathname.split("/").pop()) == null ? void 0 : _a.replace(/\.[^/.]+$/, "")) == null ? void 0 : _b.replace(/-|_/g, " ");
    if (pathTitle) {
      return pathTitle;
    }
    return parsedUrl.hostname.replace(/^www\./, "");
  } catch (e) {
    return url;
  }
}
function extractTitleFromText(text) {
  const firstLine = text.split("\n")[0];
  if (firstLine.length <= 100) {
    return firstLine;
  }
  return firstLine.substring(0, 97) + "...";
}

// src/deletion-handler.ts
function determineDeletionActions(localBookmarkIds, activeBookmarkIds, archivedBookmarkIds, settings) {
  const instructions = [];
  if (!settings.syncDeletions && !settings.handleArchivedBookmarks) {
    return instructions;
  }
  for (const bookmarkId of localBookmarkIds) {
    const isActive = activeBookmarkIds.has(bookmarkId);
    const isArchived = archivedBookmarkIds.has(bookmarkId);
    if (!isActive && !isArchived) {
      if (settings.syncDeletions && settings.deletionAction !== "ignore") {
        instructions.push({
          bookmarkId,
          action: settings.deletionAction,
          reason: "deleted"
        });
      }
    } else if (!isActive && isArchived) {
      if (settings.handleArchivedBookmarks && settings.archivedBookmarkAction !== "ignore") {
        instructions.push({
          bookmarkId,
          action: settings.archivedBookmarkAction,
          reason: "archived"
        });
      }
    }
  }
  return instructions;
}
function countDeletionResults(instructions) {
  const results = {
    deleted: 0,
    archived: 0,
    tagged: 0,
    archivedHandled: 0
  };
  for (const instruction of instructions) {
    if (instruction.reason === "deleted") {
      switch (instruction.action) {
        case "delete":
          results.deleted++;
          break;
        case "archive":
          results.archived++;
          break;
        case "tag":
          results.tagged++;
          break;
      }
    } else if (instruction.reason === "archived") {
      results.archivedHandled++;
    }
  }
  return results;
}

// src/filename-utils.ts
function sanitizeFileName(title, createdAt) {
  const date = typeof createdAt === "string" ? new Date(createdAt) : createdAt;
  const dateStr = date.toISOString().split("T")[0];
  let sanitizedTitle = title.replace(/[\\/:*?"<>|]/g, "-").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
  const maxTitleLength = 36;
  if (sanitizedTitle.length > maxTitleLength) {
    const truncated = sanitizedTitle.substring(0, maxTitleLength);
    const lastDash = truncated.lastIndexOf("-");
    if (lastDash > maxTitleLength / 2) {
      sanitizedTitle = truncated.substring(0, lastDash);
    } else {
      sanitizedTitle = truncated;
    }
  }
  return `${dateStr}-${sanitizedTitle}`;
}

// src/filter-utils.ts
function shouldIncludeBookmark(bookmarkTags, includedTags, excludedTags, isFavorited) {
  if (includedTags.length > 0) {
    const hasIncludedTag = includedTags.some((includedTag) => bookmarkTags.includes(includedTag));
    if (!hasIncludedTag) {
      return { include: false, reason: "missing_included_tag" };
    }
  }
  if (!isFavorited && excludedTags.length > 0) {
    const hasExcludedTag = excludedTags.some((excludedTag) => bookmarkTags.includes(excludedTag));
    if (hasExcludedTag) {
      return { include: false, reason: "excluded_tag" };
    }
  }
  return { include: true };
}

// src/formatting-utils.ts
function escapeYaml(str) {
  if (!str) return "";
  if (str.includes("\n") || /[:#{}\[\],&*?|<>=!%@`]/.test(str)) {
    return `|
  ${str.replace(/\n/g, "\n  ")}`;
  }
  if (str.includes('"')) {
    return `'${str}'`;
  }
  if (str.includes("'") || /^[ \t]|[ \t]$/.test(str)) {
    return `"${str.replace(/\"/g, '\\"')}"`;
  }
  return str;
}
function escapeMarkdownPath2(path) {
  if (path.includes(" ") || /[<>[\](){}]/.test(path)) {
    return `<${path}>`;
  }
  return path;
}

// src/hoarder-client.ts
var import_obsidian = require("obsidian");
var HoarderApiClient = class {
  constructor(baseUrl, apiKey, useObsidianRequest = false) {
    this.baseUrl = baseUrl.replace(/\/$/, "");
    this.apiKey = apiKey;
    this.useObsidianRequest = useObsidianRequest;
  }
  async makeRequest(endpoint, method = "GET", body, params) {
    const url = new URL(`${this.baseUrl}${endpoint}`);
    if (params) {
      Object.entries(params).forEach(([key, value]) => {
        if (value !== void 0 && value !== null) {
          url.searchParams.append(key, String(value));
        }
      });
    }
    const headers = {
      "Content-Type": "application/json",
      Authorization: `Bearer ${this.apiKey}`
    };
    try {
      if (this.useObsidianRequest) {
        const response = await (0, import_obsidian.requestUrl)({
          url: url.toString(),
          method,
          headers,
          body: body ? JSON.stringify(body) : void 0
        });
        if (response.status >= 400) {
          throw new Error(`HTTP ${response.status}: ${response.text || "Unknown error"}`);
        }
        return response.json;
      } else {
        const response = await fetch(url.toString(), {
          method,
          headers,
          body: body ? JSON.stringify(body) : void 0
        });
        if (!response.ok) {
          const errorText = await response.text();
          throw new Error(`HTTP ${response.status}: ${errorText || "Unknown error"}`);
        }
        return await response.json();
      }
    } catch (error) {
      console.error("API request failed:", url.toString(), error);
      throw error;
    }
  }
  async getBookmarks(params) {
    return this.makeRequest("/bookmarks", "GET", void 0, params);
  }
  async updateBookmark(bookmarkId, data) {
    return this.makeRequest(`/bookmarks/${bookmarkId}`, "PATCH", data);
  }
  async getBookmarkHighlights(bookmarkId) {
    return this.makeRequest(
      `/bookmarks/${bookmarkId}/highlights`,
      "GET"
    );
  }
  async getHighlights(params) {
    return this.makeRequest("/highlights", "GET", void 0, params);
  }
  async getAllHighlights() {
    const allHighlights = [];
    let cursor;
    do {
      const data = await this.getHighlights({
        limit: 100,
        cursor: cursor || void 0
      });
      allHighlights.push(...data.highlights || []);
      cursor = data.nextCursor || void 0;
    } while (cursor);
    return allHighlights;
  }
  async downloadAsset(assetId) {
    const url = `${this.baseUrl}/assets/${assetId}`;
    const headers = {
      Authorization: `Bearer ${this.apiKey}`
    };
    try {
      if (this.useObsidianRequest) {
        const response = await (0, import_obsidian.requestUrl)({
          url,
          method: "GET",
          headers
        });
        if (response.status >= 400) {
          throw new Error(`HTTP ${response.status}: ${response.text || "Unknown error"}`);
        }
        return response.arrayBuffer;
      } else {
        const response = await fetch(url, {
          method: "GET",
          headers
        });
        if (!response.ok) {
          const errorText = await response.text();
          throw new Error(`HTTP ${response.status}: ${errorText || "Unknown error"}`);
        }
        return await response.arrayBuffer();
      }
    } catch (error) {
      console.error("Asset download failed:", url, error);
      throw error;
    }
  }
  getAssetUrl(assetId) {
    const baseUrl = this.baseUrl.replace(/\/api\/v1\/?$/, "");
    return `${baseUrl}/assets/${assetId}`;
  }
};

// src/markdown-utils.ts
function extractNotesSection(content) {
  const notesMatch = content.match(/## Notes\n\n([\s\S]*?)(?=\n##|\n\[|$)/);
  return notesMatch ? notesMatch[1].trim() : null;
}

// src/message-utils.ts
function buildSyncMessage(stats) {
  let message = `Successfully synced ${stats.totalBookmarks} bookmark${stats.totalBookmarks === 1 ? "" : "s"}`;
  if (stats.skippedFiles > 0) {
    message += ` (skipped ${stats.skippedFiles} existing file${stats.skippedFiles === 1 ? "" : "s"})`;
  }
  if (stats.updatedInHoarder > 0) {
    message += ` and updated ${stats.updatedInHoarder} note${stats.updatedInHoarder === 1 ? "" : "s"} in Karakeep`;
  }
  if (stats.excludedByTags > 0) {
    message += `, excluded ${stats.excludedByTags} bookmark${stats.excludedByTags === 1 ? "" : "s"} by tags`;
  }
  if (stats.includedByTags > 0 && stats.includedTagsEnabled) {
    message += `, included ${stats.includedByTags} bookmark${stats.includedByTags === 1 ? "" : "s"} by tags`;
  }
  if (stats.skippedNoHighlights > 0) {
    message += `, skipped ${stats.skippedNoHighlights} bookmark${stats.skippedNoHighlights === 1 ? "" : "s"} without highlights`;
  }
  const totalDeleted = stats.deletionResults.deleted + stats.deletionResults.archived + stats.deletionResults.tagged;
  const totalArchived = stats.deletionResults.archivedHandled;
  if (totalDeleted > 0 || totalArchived > 0) {
    if (totalDeleted > 0) {
      message += `, processed ${totalDeleted} deleted bookmark${totalDeleted === 1 ? "" : "s"}`;
      if (stats.deletionResults.deleted > 0) {
        message += ` (${stats.deletionResults.deleted} deleted)`;
      }
      if (stats.deletionResults.archived > 0) {
        message += ` (${stats.deletionResults.archived} archived)`;
      }
      if (stats.deletionResults.tagged > 0) {
        message += ` (${stats.deletionResults.tagged} tagged)`;
      }
    }
    if (totalArchived > 0) {
      message += `, handled ${totalArchived} archived bookmark${totalArchived === 1 ? "" : "s"}`;
    }
  }
  return message;
}

// src/settings.ts
var import_obsidian2 = require("obsidian");
var DEFAULT_SETTINGS = {
  apiKey: "",
  apiEndpoint: "https://api.hoarder.app/api/v1",
  syncFolder: "Hoarder",
  attachmentsFolder: "Hoarder/attachments",
  syncIntervalMinutes: 60,
  lastSyncTimestamp: 0,
  updateExistingFiles: false,
  excludeArchived: true,
  onlyFavorites: false,
  syncNotesToHoarder: true,
  syncHighlights: true,
  onlyBookmarksWithHighlights: false,
  excludedTags: [],
  includedTags: [],
  downloadAssets: true,
  syncDeletions: false,
  deletionAction: "delete",
  deletionTag: "deleted",
  archiveFolder: "Hoarder/deleted",
  handleArchivedBookmarks: false,
  archivedBookmarkAction: "delete",
  archivedBookmarkTag: "archived",
  archivedBookmarkFolder: "Hoarder/archived",
  useObsidianRequest: false
};
var FolderSuggest = class extends import_obsidian2.AbstractInputSuggest {
  constructor(app, inputEl) {
    super(app, inputEl);
    this.folders = this.getFolders();
    this.inputEl = inputEl;
  }
  getSuggestions(inputStr) {
    const lowerCaseInputStr = inputStr.toLowerCase();
    return this.folders.filter((folder) => folder.path.toLowerCase().contains(lowerCaseInputStr));
  }
  renderSuggestion(folder, el) {
    el.setText(folder.path);
  }
  selectSuggestion(folder) {
    const value = folder.path;
    this.inputEl.value = value;
    this.inputEl.trigger("input");
    this.close();
  }
  getFolders() {
    const folders = [];
    this.app.vault.getAllLoadedFiles().forEach((file) => {
      if (file instanceof import_obsidian2.TFolder) {
        folders.push(file);
      }
    });
    return folders.sort((a, b) => a.path.localeCompare(b.path));
  }
};
var HoarderSettingTab = class extends import_obsidian2.PluginSettingTab {
  constructor(app, plugin) {
    super(app, plugin);
    this.updateSyncButton = (isSyncing) => {
      if (this.syncButton) {
        this.syncButton.setButtonText(isSyncing ? "Syncing..." : "Sync Now");
        this.syncButton.setDisabled(isSyncing);
      }
    };
    this.plugin = plugin;
  }
  onunload() {
    this.plugin.events.off("sync-state-change", this.updateSyncButton);
  }
  display() {
    const { containerEl } = this;
    containerEl.empty();
    containerEl.createEl("h3", { text: "API Configuration" });
    containerEl.createEl("div", {
      text: "Connection settings for your Karakeep instance",
      cls: "setting-item-description"
    });
    new import_obsidian2.Setting(containerEl).setName("Api key").setDesc("Your Hoarder API key").addText(
      (text) => text.setPlaceholder("Enter your API key").setValue(this.plugin.settings.apiKey).onChange(async (value) => {
        this.plugin.settings.apiKey = value;
        await this.plugin.saveSettings();
      }).inputEl.addClass("hoarder-wide-input")
    );
    new import_obsidian2.Setting(containerEl).setName("Api endpoint").setDesc("Hoarder API endpoint URL (default: https://api.karakeep.app/api/v1)").addText(
      (text) => text.setPlaceholder("Enter API endpoint").setValue(this.plugin.settings.apiEndpoint).onChange(async (value) => {
        this.plugin.settings.apiEndpoint = value;
        await this.plugin.saveSettings();
      }).inputEl.addClass("hoarder-wide-input")
    );
    new import_obsidian2.Setting(containerEl).setName("Bypass CORS").setDesc(
      "Use Obsidian's internal request method to avoid CORS issues. Enable this if you're experiencing connection problems."
    ).addToggle(
      (toggle) => toggle.setValue(this.plugin.settings.useObsidianRequest).onChange(async (value) => {
        this.plugin.settings.useObsidianRequest = value;
        await this.plugin.saveSettings();
      })
    );
    containerEl.createEl("h3", { text: "File Organization" });
    containerEl.createEl("div", {
      text: "Configure where your bookmarks and assets are stored",
      cls: "setting-item-description"
    });
    new import_obsidian2.Setting(containerEl).setName("Sync folder").setDesc("Folder where bookmarks will be saved").addText((text) => {
      text.setPlaceholder("Example: folder1/folder2").setValue(this.plugin.settings.syncFolder).onChange(async (value) => {
        this.plugin.settings.syncFolder = value;
        await this.plugin.saveSettings();
      });
      text.inputEl.addClass("hoarder-medium-input");
      new FolderSuggest(this.app, text.inputEl);
      return text;
    });
    new import_obsidian2.Setting(containerEl).setName("Attachments folder").setDesc("Folder where bookmark images will be saved").addText((text) => {
      text.setPlaceholder("Example: folder1/attachments").setValue(this.plugin.settings.attachmentsFolder).onChange(async (value) => {
        this.plugin.settings.attachmentsFolder = value;
        await this.plugin.saveSettings();
      });
      text.inputEl.addClass("hoarder-medium-input");
      new FolderSuggest(this.app, text.inputEl);
      return text;
    });
    containerEl.createEl("h3", { text: "Sync Behavior" });
    containerEl.createEl("div", {
      text: "Control how synchronization works",
      cls: "setting-item-description"
    });
    new import_obsidian2.Setting(containerEl).setName("Sync interval").setDesc("How often to sync (in minutes)").addText(
      (text) => text.setPlaceholder("60").setValue(String(this.plugin.settings.syncIntervalMinutes)).onChange(async (value) => {
        const numValue = parseInt(value);
        if (!isNaN(numValue) && numValue > 0) {
          this.plugin.settings.syncIntervalMinutes = numValue;
          await this.plugin.saveSettings();
          this.plugin.startPeriodicSync();
        }
      }).inputEl.addClass("hoarder-small-input")
    );
    new import_obsidian2.Setting(containerEl).setName("Update existing files").setDesc(
      "Whether to update existing bookmark files when remote data changes. When disabled, only new bookmarks will be created."
    ).addToggle(
      (toggle) => toggle.setValue(this.plugin.settings.updateExistingFiles).onChange(async (value) => {
        this.plugin.settings.updateExistingFiles = value;
        await this.plugin.saveSettings();
      })
    );
    new import_obsidian2.Setting(containerEl).setName("Sync notes to Karakeep").setDesc("Whether to sync notes to Karakeep").addToggle(
      (toggle) => toggle.setValue(this.plugin.settings.syncNotesToHoarder).onChange(async (value) => {
        this.plugin.settings.syncNotesToHoarder = value;
        await this.plugin.saveSettings();
      })
    );
    new import_obsidian2.Setting(containerEl).setName("Sync highlights").setDesc("Whether to sync highlights from Karakeep into bookmark files").addToggle(
      (toggle) => toggle.setValue(this.plugin.settings.syncHighlights).onChange(async (value) => {
        this.plugin.settings.syncHighlights = value;
        await this.plugin.saveSettings();
      })
    );
    new import_obsidian2.Setting(containerEl).setName("Download assets").setDesc(
      "Download images and other assets locally (if disabled, assets will be embedded using their source URLs)"
    ).addToggle(
      (toggle) => toggle.setValue(this.plugin.settings.downloadAssets).onChange(async (value) => {
        this.plugin.settings.downloadAssets = value;
        await this.plugin.saveSettings();
      })
    );
    containerEl.createEl("h3", { text: "Sync Filtering" });
    containerEl.createEl("div", {
      text: "Control which bookmarks are synchronized",
      cls: "setting-item-description"
    });
    new import_obsidian2.Setting(containerEl).setName("Exclude archived").setDesc("Exclude archived bookmarks from sync").addToggle(
      (toggle) => toggle.setValue(this.plugin.settings.excludeArchived).onChange(async (value) => {
        this.plugin.settings.excludeArchived = value;
        await this.plugin.saveSettings();
      })
    );
    new import_obsidian2.Setting(containerEl).setName("Only favorites").setDesc("Only sync favorited bookmarks").addToggle(
      (toggle) => toggle.setValue(this.plugin.settings.onlyFavorites).onChange(async (value) => {
        this.plugin.settings.onlyFavorites = value;
        await this.plugin.saveSettings();
      })
    );
    new import_obsidian2.Setting(containerEl).setName("Only bookmarks with highlights").setDesc(
      "Only sync bookmarks that have highlights (requires 'Sync highlights' to be enabled)"
    ).addToggle(
      (toggle) => toggle.setValue(this.plugin.settings.onlyBookmarksWithHighlights).onChange(async (value) => {
        this.plugin.settings.onlyBookmarksWithHighlights = value;
        await this.plugin.saveSettings();
      })
    );
    new import_obsidian2.Setting(containerEl).setName("Excluded tags").setDesc("Bookmarks with these tags will not be synced (comma-separated), unless favorited").addText(
      (text) => text.setPlaceholder("private, secret, draft").setValue(this.plugin.settings.excludedTags.join(", ")).onChange(async (value) => {
        this.plugin.settings.excludedTags = value.split(",").map((tag) => tag.trim()).filter((tag) => tag.length > 0);
        await this.plugin.saveSettings();
      }).inputEl.addClass("hoarder-wide-input")
    );
    new import_obsidian2.Setting(containerEl).setName("Included tags").setDesc("Bookmarks with these tags will be synced (comma-separated)").addText(
      (text) => text.setPlaceholder("public, shared").setValue(this.plugin.settings.includedTags.join(", ")).onChange(async (value) => {
        this.plugin.settings.includedTags = value.split(",").map((tag) => tag.trim()).filter((tag) => tag.length > 0);
        await this.plugin.saveSettings();
      }).inputEl.addClass("hoarder-wide-input")
    );
    containerEl.createEl("h3", { text: "Deletion Handling" });
    containerEl.createEl("div", {
      text: "Configure what happens when bookmarks are deleted in Karakeep",
      cls: "setting-item-description"
    });
    const syncDeletionsToggle = new import_obsidian2.Setting(containerEl).setName("Sync deletions").setDesc("Automatically handle bookmarks that are deleted in Karakeep").addToggle(
      (toggle) => toggle.setValue(this.plugin.settings.syncDeletions).onChange(async (value) => {
        this.plugin.settings.syncDeletions = value;
        await this.plugin.saveSettings();
        this.display();
      })
    );
    if (this.plugin.settings.syncDeletions) {
      const deletionActionSetting = new import_obsidian2.Setting(containerEl).setName("Deletion action").setDesc("What to do with local files when bookmarks are deleted in Karakeep").addDropdown(
        (dropdown) => dropdown.addOption("delete", "Delete file").addOption("archive", "Move to archive folder").addOption("tag", "Add deletion tag").setValue(this.plugin.settings.deletionAction).onChange(async (value) => {
          this.plugin.settings.deletionAction = value;
          await this.plugin.saveSettings();
          this.display();
        })
      );
      if (this.plugin.settings.deletionAction === "archive") {
        new import_obsidian2.Setting(containerEl).setName("Archive folder").setDesc("Folder to move deleted bookmarks to").addText((text) => {
          text.setPlaceholder("Example: Hoarder/deleted").setValue(this.plugin.settings.archiveFolder).onChange(async (value) => {
            this.plugin.settings.archiveFolder = value;
            await this.plugin.saveSettings();
          });
          text.inputEl.addClass("hoarder-medium-input");
          new FolderSuggest(this.app, text.inputEl);
          return text;
        });
      }
      if (this.plugin.settings.deletionAction === "tag") {
        new import_obsidian2.Setting(containerEl).setName("Deletion tag").setDesc("Tag to add to files when bookmarks are deleted").addText(
          (text) => text.setPlaceholder("deleted").setValue(this.plugin.settings.deletionTag).onChange(async (value) => {
            this.plugin.settings.deletionTag = value;
            await this.plugin.saveSettings();
          }).inputEl.addClass("hoarder-medium-input")
        );
      }
    }
    containerEl.createEl("h3", { text: "Archive Handling" });
    containerEl.createEl("div", {
      text: "Configure what happens when bookmarks are archived in Karakeep",
      cls: "setting-item-description"
    });
    const handleArchivedToggle = new import_obsidian2.Setting(containerEl).setName("Handle archived bookmarks").setDesc("Separately handle bookmarks that are archived (not deleted) in Karakeep").addToggle(
      (toggle) => toggle.setValue(this.plugin.settings.handleArchivedBookmarks).onChange(async (value) => {
        this.plugin.settings.handleArchivedBookmarks = value;
        await this.plugin.saveSettings();
        this.display();
      })
    );
    if (this.plugin.settings.handleArchivedBookmarks) {
      const archivedActionSetting = new import_obsidian2.Setting(containerEl).setName("Archived bookmark action").setDesc("What to do with local files when bookmarks are archived in Karakeep").addDropdown(
        (dropdown) => dropdown.addOption("ignore", "Do nothing").addOption("delete", "Delete file").addOption("archive", "Move to archive folder").addOption("tag", "Add archived tag").setValue(this.plugin.settings.archivedBookmarkAction).onChange(async (value) => {
          this.plugin.settings.archivedBookmarkAction = value;
          await this.plugin.saveSettings();
          this.display();
        })
      );
      if (this.plugin.settings.archivedBookmarkAction === "archive") {
        new import_obsidian2.Setting(containerEl).setName("Archived bookmark folder").setDesc("Folder to move archived bookmarks to").addText((text) => {
          text.setPlaceholder("Example: Hoarder/archived").setValue(this.plugin.settings.archivedBookmarkFolder).onChange(async (value) => {
            this.plugin.settings.archivedBookmarkFolder = value;
            await this.plugin.saveSettings();
          });
          text.inputEl.addClass("hoarder-medium-input");
          new FolderSuggest(this.app, text.inputEl);
          return text;
        });
      }
      if (this.plugin.settings.archivedBookmarkAction === "tag") {
        new import_obsidian2.Setting(containerEl).setName("Archived bookmark tag").setDesc("Tag to add to files when bookmarks are archived").addText(
          (text) => text.setPlaceholder("archived").setValue(this.plugin.settings.archivedBookmarkTag).onChange(async (value) => {
            this.plugin.settings.archivedBookmarkTag = value;
            await this.plugin.saveSettings();
          }).inputEl.addClass("hoarder-medium-input")
        );
      }
    }
    containerEl.createEl("h3", { text: "Manual Actions & Status" });
    containerEl.createEl("div", {
      text: "Manual sync controls and synchronization status",
      cls: "setting-item-description"
    });
    new import_obsidian2.Setting(containerEl).setName("Manual sync").setDesc("Sync bookmarks now").addButton((button) => {
      this.syncButton = button.setButtonText(this.plugin.isSyncing ? "Syncing..." : "Sync Now").setDisabled(this.plugin.isSyncing).onClick(async () => {
        const result = await this.plugin.syncBookmarks();
        new import_obsidian2.Notice(result.message);
      });
      this.plugin.events.on("sync-state-change", this.updateSyncButton);
      return button;
    });
    if (this.plugin.settings.lastSyncTimestamp > 0) {
      containerEl.createEl("div", {
        text: `Last synced: ${new Date(this.plugin.settings.lastSyncTimestamp).toLocaleString()}`,
        cls: "setting-item-description"
      });
    }
  }
};

// src/tag-utils.ts
function sanitizeTag(tag) {
  let sanitized = tag.trim();
  if (!sanitized) return null;
  sanitized = sanitized.replace(/\s+/g, "-");
  sanitized = sanitized.replace(/[^a-zA-Z0-9_\-/]/g, "");
  if (!sanitized) return null;
  if (/^\d+$/.test(sanitized)) {
    sanitized = "tag-" + sanitized;
  }
  if (/^[\d\/\-_]+$/.test(sanitized)) {
    sanitized = "tag-" + sanitized;
  }
  return sanitized;
}
function sanitizeTags(tags) {
  return tags.map(sanitizeTag).filter((tag) => tag !== null);
}

// src/main.ts
var HoarderPlugin = class extends import_obsidian3.Plugin {
  constructor() {
    super(...arguments);
    this.isSyncing = false;
    this.skippedFiles = 0;
    this.events = new import_obsidian3.Events();
    this.modificationTimeout = null;
    this.lastSyncedNotes = null;
    this.client = null;
  }
  async onload() {
    await this.loadSettings();
    this.initializeClient();
    this.addSettingTab(new HoarderSettingTab(this.app, this));
    this.addCommand({
      id: "trigger-hoarder-sync",
      name: "Sync Bookmarks",
      callback: async () => {
        const result = await this.syncBookmarks();
        new import_obsidian3.Notice(result.message);
      }
    });
    this.registerEvent(
      this.app.vault.on("modify", async (file) => {
        if (this.settings.syncNotesToHoarder && file.path.startsWith(this.settings.syncFolder) && file.path.endsWith(".md") && file instanceof import_obsidian3.TFile) {
          if (this.modificationTimeout) {
            window.clearTimeout(this.modificationTimeout);
          }
          this.modificationTimeout = window.setTimeout(async () => {
            await this.handleFileModification(file);
          }, 2e3);
        }
      })
    );
    this.startPeriodicSync();
  }
  onunload() {
    if (this.syncIntervalId) {
      window.clearInterval(this.syncIntervalId);
    }
    if (this.modificationTimeout) {
      window.clearTimeout(this.modificationTimeout);
    }
  }
  async loadSettings() {
    this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
  }
  async saveSettings() {
    await this.saveData(this.settings);
    this.initializeClient();
  }
  startPeriodicSync() {
    if (this.syncIntervalId) {
      window.clearInterval(this.syncIntervalId);
    }
    const interval = this.settings.syncIntervalMinutes * 60 * 1e3;
    this.syncBookmarks();
    this.syncIntervalId = window.setInterval(() => {
      this.syncBookmarks();
    }, interval);
  }
  async fetchBookmarks(cursor, limit = 100) {
    if (!this.client) {
      throw new Error("Client not initialized");
    }
    return await this.client.getBookmarks({
      limit,
      cursor: cursor || void 0,
      archived: this.settings.excludeArchived ? false : void 0,
      favourited: this.settings.onlyFavorites ? true : void 0
    });
  }
  async fetchAllBookmarks(includeArchived = false) {
    if (!this.client) {
      throw new Error("Client not initialized");
    }
    const allBookmarks = [];
    let cursor;
    do {
      const data = await this.client.getBookmarks({
        limit: 100,
        cursor: cursor || void 0,
        archived: includeArchived ? void 0 : false,
        favourited: this.settings.onlyFavorites ? true : void 0
      });
      allBookmarks.push(...data.bookmarks || []);
      cursor = data.nextCursor || void 0;
    } while (cursor);
    return allBookmarks;
  }
  async extractNotesFromFile(filePath) {
    var _a, _b;
    try {
      const file = this.app.vault.getAbstractFileByPath(filePath);
      if (!(file instanceof import_obsidian3.TFile)) {
        return { currentNotes: null, originalNotes: null };
      }
      const content = await this.app.vault.adapter.read(filePath);
      const currentNotes = extractNotesSection(content);
      const metadata = (_a = this.app.metadataCache.getFileCache(file)) == null ? void 0 : _a.frontmatter;
      const originalNotes = (_b = metadata == null ? void 0 : metadata.original_note) != null ? _b : null;
      return { currentNotes, originalNotes };
    } catch (error) {
      console.error("Error reading file:", error);
      return { currentNotes: null, originalNotes: null };
    }
  }
  async updateBookmarkInHoarder(bookmarkId, note) {
    try {
      if (!this.client) {
        throw new Error("Client not initialized");
      }
      await this.client.updateBookmark(bookmarkId, { note });
      return true;
    } catch (error) {
      console.error("Error updating bookmark in Hoarder:", error);
      return false;
    }
  }
  setSyncing(value) {
    this.isSyncing = value;
    this.events.trigger("sync-state-change", value);
  }
  async getLocalBookmarkFiles() {
    var _a;
    const bookmarkFiles = /* @__PURE__ */ new Map();
    const folderPath = this.settings.syncFolder;
    if (!await this.app.vault.adapter.exists(folderPath)) {
      return bookmarkFiles;
    }
    const files = this.app.vault.getMarkdownFiles();
    for (const file of files) {
      if (file.path.startsWith(folderPath) && file.path.endsWith(".md")) {
        const metadata = (_a = this.app.metadataCache.getFileCache(file)) == null ? void 0 : _a.frontmatter;
        const bookmarkId = metadata == null ? void 0 : metadata.bookmark_id;
        if (bookmarkId) {
          bookmarkFiles.set(bookmarkId, file.path);
        }
      }
    }
    return bookmarkFiles;
  }
  async handleDeletedAndArchivedBookmarks(localBookmarkFiles, activeBookmarkIds, archivedBookmarkIds) {
    const deletionSettings = {
      syncDeletions: this.settings.syncDeletions,
      deletionAction: this.settings.deletionAction,
      handleArchivedBookmarks: this.settings.handleArchivedBookmarks,
      archivedBookmarkAction: this.settings.archivedBookmarkAction
    };
    const localBookmarkIds = Array.from(localBookmarkFiles.keys());
    const instructions = determineDeletionActions(
      localBookmarkIds,
      activeBookmarkIds,
      archivedBookmarkIds,
      deletionSettings
    );
    for (const instruction of instructions) {
      const filePath = localBookmarkFiles.get(instruction.bookmarkId);
      if (!filePath) continue;
      const file = this.app.vault.getAbstractFileByPath(filePath);
      if (!(file instanceof import_obsidian3.TFile)) continue;
      try {
        switch (instruction.action) {
          case "delete":
            await this.app.vault.delete(file);
            break;
          case "archive":
            const archiveFolder = instruction.reason === "deleted" ? this.settings.archiveFolder : this.settings.archivedBookmarkFolder;
            await this.moveToArchiveFolder(file, archiveFolder);
            break;
          case "tag":
            const tag = instruction.reason === "deleted" ? this.settings.deletionTag : this.settings.archivedBookmarkTag;
            await this.addDeletionTag(file, tag);
            break;
        }
      } catch (error) {
        console.error(`Error handling bookmark ${instruction.bookmarkId}:`, error);
      }
    }
    return countDeletionResults(instructions);
  }
  async moveToArchiveFolder(file, archiveFolder) {
    if (!archiveFolder) {
      throw new Error("Archive folder not configured");
    }
    if (!await this.app.vault.adapter.exists(archiveFolder)) {
      await this.app.vault.createFolder(archiveFolder);
    }
    const fileName = file.name;
    const newPath = `${archiveFolder}/${fileName}`;
    let finalPath = newPath;
    let counter = 1;
    while (await this.app.vault.adapter.exists(finalPath)) {
      const nameWithoutExt = fileName.replace(/\.md$/, "");
      finalPath = `${archiveFolder}/${nameWithoutExt}-${counter}.md`;
      counter++;
    }
    await this.app.fileManager.renameFile(file, finalPath);
  }
  async addDeletionTag(file, tag) {
    if (!tag) {
      throw new Error("Tag not configured");
    }
    await this.app.fileManager.processFrontMatter(file, (frontmatter) => {
      if (!frontmatter.tags) {
        frontmatter.tags = [];
      }
      if (typeof frontmatter.tags === "string") {
        frontmatter.tags = [frontmatter.tags];
      }
      if (!frontmatter.tags.includes(tag)) {
        frontmatter.tags.push(tag);
      }
    });
  }
  async syncBookmarks() {
    var _a;
    if (this.isSyncing) {
      console.error("[Hoarder] Sync already in progress");
      return { success: false, message: "Sync already in progress" };
    }
    if (!this.settings.apiKey) {
      console.log("[Hoarder] API key not configured");
      return { success: false, message: "Hoarder API key not configured" };
    }
    console.log("[Hoarder] Starting sync...");
    console.log(
      `[Hoarder] Settings: syncNotesToHoarder=${this.settings.syncNotesToHoarder}, updateExistingFiles=${this.settings.updateExistingFiles}`
    );
    this.setSyncing(true);
    let totalBookmarks = 0;
    this.skippedFiles = 0;
    let updatedInHoarder = 0;
    let excludedByTags = 0;
    let includedByTags = 0;
    let totalBookmarksProcessed = 0;
    let skippedNoHighlights = 0;
    try {
      const folderPath = this.settings.syncFolder;
      if (!await this.app.vault.adapter.exists(folderPath)) {
        await this.app.vault.createFolder(folderPath);
      }
      const localBookmarkFiles = await this.getLocalBookmarkFiles();
      const activeBookmarks = await this.fetchAllBookmarks(false);
      const allBookmarks = await this.fetchAllBookmarks(true);
      const activeBookmarkIds = new Set(activeBookmarks.map((b) => b.id));
      const allBookmarkIds = new Set(allBookmarks.map((b) => b.id));
      const archivedBookmarkIds = new Set(
        allBookmarks.filter((b) => b.archived && !activeBookmarkIds.has(b.id)).map((b) => b.id)
      );
      let highlightsByBookmarkId = /* @__PURE__ */ new Map();
      let bookmarkIdsWithHighlights = /* @__PURE__ */ new Set();
      if ((this.settings.syncHighlights || this.settings.onlyBookmarksWithHighlights) && this.client) {
        try {
          const allHighlights = await this.client.getAllHighlights();
          for (const highlight of allHighlights) {
            if (!highlightsByBookmarkId.has(highlight.bookmarkId)) {
              highlightsByBookmarkId.set(highlight.bookmarkId, []);
            }
            highlightsByBookmarkId.get(highlight.bookmarkId).push(highlight);
            bookmarkIdsWithHighlights.add(highlight.bookmarkId);
          }
        } catch (error) {
          console.error("Error fetching highlights in bulk:", error);
        }
      }
      let cursor;
      do {
        const result = await this.fetchBookmarks(cursor);
        const bookmarks = result.bookmarks || [];
        cursor = result.nextCursor || void 0;
        totalBookmarksProcessed += bookmarks.length;
        for (const bookmark of bookmarks) {
          if (this.settings.onlyBookmarksWithHighlights && !bookmarkIdsWithHighlights.has(bookmark.id)) {
            skippedNoHighlights++;
            continue;
          }
          const bookmarkTags = bookmark.tags.map((tag) => tag.name.toLowerCase());
          const filterResult = shouldIncludeBookmark(
            bookmarkTags,
            this.settings.includedTags.map((t) => t.toLowerCase()),
            this.settings.excludedTags.map((t) => t.toLowerCase()),
            bookmark.favourited
          );
          if (!filterResult.include) {
            excludedByTags++;
            continue;
          }
          if (this.settings.includedTags.length > 0) {
            includedByTags++;
          }
          const title = getBookmarkTitle(bookmark);
          const fileName = `${folderPath}/${sanitizeFileName(title, bookmark.createdAt)}.md`;
          const highlights = highlightsByBookmarkId.get(bookmark.id) || [];
          const fileExists = await this.app.vault.adapter.exists(fileName);
          if (fileExists) {
            const file = this.app.vault.getAbstractFileByPath(fileName);
            if (file instanceof import_obsidian3.TFile) {
              const metadata = (_a = this.app.metadataCache.getFileCache(file)) == null ? void 0 : _a.frontmatter;
              const storedModifiedTime = (metadata == null ? void 0 : metadata.modified) ? new Date(metadata.modified).getTime() : 0;
              const bookmarkModifiedTime = bookmark.modifiedAt ? new Date(bookmark.modifiedAt).getTime() : new Date(bookmark.createdAt).getTime();
              let hasNewHighlights = false;
              if (this.settings.syncHighlights && highlights.length > 0) {
                const newestHighlightTime = Math.max(
                  ...highlights.map((h) => new Date(h.createdAt).getTime())
                );
                hasNewHighlights = !storedModifiedTime || newestHighlightTime > storedModifiedTime;
              }
              if (!this.settings.updateExistingFiles) {
                this.skippedFiles++;
                continue;
              }
              if (this.settings.syncNotesToHoarder) {
                const { currentNotes, originalNotes } = await this.extractNotesFromFile(fileName);
                const remoteNotes = bookmark.note || "";
                if (originalNotes === null && currentNotes !== null) {
                  console.debug(`[Hoarder] original_note missing for ${fileName}`);
                  if (currentNotes !== remoteNotes) {
                    console.log(`[Hoarder] Local notes differ from remote, syncing to Hoarder`);
                    const updated = await this.updateBookmarkInHoarder(bookmark.id, currentNotes);
                    if (updated) {
                      updatedInHoarder++;
                      bookmark.note = currentNotes;
                      this.lastSyncedNotes = currentNotes;
                      console.debug(
                        `[Hoarder] Initializing original_note to synced value for ${fileName}`
                      );
                      await this.app.fileManager.processFrontMatter(file, (frontmatter) => {
                        frontmatter["original_note"] = currentNotes;
                      });
                    }
                  } else {
                    console.debug(
                      `[Hoarder] Notes match remote, skipping original_note initialization to preserve mtime`
                    );
                  }
                } else if (currentNotes !== null && originalNotes !== null && currentNotes !== originalNotes && currentNotes !== remoteNotes) {
                  console.log(`[Hoarder] Local notes changed for ${fileName}, syncing to Hoarder`);
                  const updated = await this.updateBookmarkInHoarder(bookmark.id, currentNotes);
                  if (updated) {
                    updatedInHoarder++;
                    bookmark.note = currentNotes;
                    this.lastSyncedNotes = currentNotes;
                  }
                }
              }
              const newContent = await this.formatBookmarkAsMarkdown(bookmark, title, highlights);
              const existingContent = await this.app.vault.adapter.read(fileName);
              if (existingContent !== newContent) {
                await this.app.vault.adapter.write(fileName, newContent);
                totalBookmarks++;
              } else {
                this.skippedFiles++;
              }
            }
          } else {
            const content = await this.formatBookmarkAsMarkdown(bookmark, title, highlights);
            await this.app.vault.create(fileName, content);
            totalBookmarks++;
          }
        }
      } while (cursor);
      const deletionResults = await this.handleDeletedAndArchivedBookmarks(
        localBookmarkFiles,
        activeBookmarkIds,
        archivedBookmarkIds
      );
      this.settings.lastSyncTimestamp = Date.now();
      await this.saveSettings();
      const stats = {
        totalBookmarks,
        skippedFiles: this.skippedFiles,
        updatedInHoarder,
        excludedByTags,
        includedByTags,
        includedTagsEnabled: this.settings.includedTags.length > 0,
        skippedNoHighlights,
        deletionResults
      };
      const message = buildSyncMessage(stats);
      return {
        success: true,
        message
      };
    } catch (error) {
      console.error("Error syncing bookmarks:", error);
      return {
        success: false,
        message: `Error syncing: ${error.message}`
      };
    } finally {
      this.setSyncing(false);
      this.skippedFiles = 0;
    }
  }
  async formatBookmarkAsMarkdown(bookmark, title, highlights) {
    const url = bookmark.content.type === "link" ? bookmark.content.url : bookmark.content.sourceUrl;
    const description = bookmark.content.type === "link" ? bookmark.content.description : bookmark.content.text;
    const rawTags = bookmark.tags.map((tag) => tag.name);
    const tags = sanitizeTags(rawTags);
    const { content: assetContent, frontmatter: assetsFm } = await processBookmarkAssets(
      this.app,
      bookmark,
      title,
      this.client,
      this.settings
    );
    let assetsYaml = "";
    if (assetsFm) {
      const lines = [];
      if (assetsFm.image) lines.push(`image: ${assetsFm.image}`);
      if (assetsFm.banner) lines.push(`banner: ${assetsFm.banner}`);
      if (assetsFm.screenshot) lines.push(`screenshot: ${assetsFm.screenshot}`);
      if (assetsFm.full_page_archive)
        lines.push(`full_page_archive: ${assetsFm.full_page_archive}`);
      if (assetsFm.video) lines.push(`video: ${assetsFm.video}`);
      if (assetsFm.additional && assetsFm.additional.length > 0) {
        lines.push("additional:");
        for (const link of assetsFm.additional) {
          lines.push(`  - ${link}`);
        }
      }
      assetsYaml = lines.join("\n") + "\n";
    }
    const tagsYaml = tags.length > 0 ? `tags:
  - ${tags.join("\n  - ")}
` : "";
    let content = `---
bookmark_id: "${bookmark.id}"
url: ${escapeYaml(url)}
title: ${escapeYaml(title)}
date: ${new Date(bookmark.createdAt).toISOString()}
${bookmark.modifiedAt ? `modified: ${new Date(bookmark.modifiedAt).toISOString()}
` : ""}${tagsYaml}note: ${escapeYaml(bookmark.note)}
original_note: ${escapeYaml(bookmark.note)}
summary: ${escapeYaml(bookmark.summary)}
${assetsYaml}
---

# ${title}
`;
    content += assetContent;
    if (bookmark.summary) {
      content += `
## Summary

${bookmark.summary}
`;
    }
    if (description) {
      content += `
## Description

${description}
`;
    }
    if (highlights && highlights.length > 0 && this.settings.syncHighlights) {
      content += `
## Highlights

`;
      const sortedHighlights = highlights.sort((a, b) => a.startOffset - b.startOffset);
      for (const highlight of sortedHighlights) {
        const date = new Date(highlight.createdAt).toLocaleDateString("en-US", {
          year: "numeric",
          month: "long",
          day: "numeric"
        });
        content += `> [!karakeep-${highlight.color}] ${date}
`;
        const highlightLines = highlight.text.split("\n");
        for (const line of highlightLines) {
          content += `> ${line}
`;
        }
        if (highlight.note && highlight.note.trim()) {
          content += `>
`;
          const noteLines = highlight.note.split("\n");
          for (let i = 0; i < noteLines.length; i++) {
            if (i === 0) {
              content += `> *Note: ${noteLines[i]}*
`;
            } else {
              content += `> *${noteLines[i]}*
`;
            }
          }
        }
        content += `
`;
      }
    }
    content += `
## Notes

${bookmark.note || ""}
`;
    if (url && bookmark.content.type !== "asset") {
      content += `
[Visit Link](${escapeMarkdownPath2(url)})
`;
    }
    const hoarderUrl = `${this.settings.apiEndpoint.replace("/api/v1", "/dashboard/preview")}/${bookmark.id}`;
    content += `
[View in Hoarder](${escapeMarkdownPath2(hoarderUrl)})`;
    return content;
  }
  async handleFileModification(file) {
    var _a;
    try {
      console.debug(`[Hoarder] File modified: ${file.path}`);
      const { currentNotes, originalNotes } = await this.extractNotesFromFile(file.path);
      const currentNotesStr = currentNotes || "";
      const originalNotesStr = originalNotes || "";
      console.log(
        `[Hoarder] Current notes length: ${currentNotesStr.length}, Original notes length: ${originalNotesStr.length}`
      );
      if (currentNotesStr === this.lastSyncedNotes) {
        console.log("[Hoarder] Skipping - notes match last synced version");
        return;
      }
      const metadata = (_a = this.app.metadataCache.getFileCache(file)) == null ? void 0 : _a.frontmatter;
      const bookmarkId = metadata == null ? void 0 : metadata.bookmark_id;
      if (!bookmarkId) {
        console.log("[Hoarder] No bookmark_id found in frontmatter");
        return;
      }
      console.log(`[Hoarder] Bookmark ID: ${bookmarkId}`);
      if (originalNotes === null) {
        const frontmatterNote = (metadata == null ? void 0 : metadata.note) || "";
        console.log(`[Hoarder] original_note is null for ${file.path}`);
        if (currentNotesStr !== frontmatterNote) {
          console.log("[Hoarder] Notes have changed from frontmatter note");
          const success = await this.updateBookmarkInHoarder(bookmarkId, currentNotesStr);
          if (success) {
            this.lastSyncedNotes = currentNotesStr;
            setTimeout(async () => {
              try {
                const { currentNotes: latestNotes, originalNotes: currentOriginalNotes } = await this.extractNotesFromFile(file.path);
                if (latestNotes === currentNotesStr && currentOriginalNotes !== currentNotesStr) {
                  await this.app.fileManager.processFrontMatter(file, (frontmatter) => {
                    frontmatter["original_note"] = currentNotesStr;
                  });
                  console.debug("[Hoarder] Initialized and updated original_note in frontmatter");
                } else if (currentOriginalNotes === currentNotesStr) {
                  console.debug(
                    "[Hoarder] original_note already initialized, skipping frontmatter update"
                  );
                }
              } catch (error) {
                console.error("[Hoarder] Error updating frontmatter:", error);
              }
            }, 5e3);
            new import_obsidian3.Notice("Notes synced to Hoarder");
          } else {
            console.error("[Hoarder] Failed to update bookmark in Hoarder");
          }
        } else {
          console.debug(
            "[Hoarder] Notes match frontmatter, skipping original_note initialization to preserve mtime"
          );
        }
        return;
      }
      if (currentNotesStr !== originalNotesStr) {
        console.log("[Hoarder] Notes have changed, syncing to Hoarder");
        const updated = await this.updateBookmarkInHoarder(bookmarkId, currentNotesStr);
        if (updated) {
          this.lastSyncedNotes = currentNotesStr;
          console.log("[Hoarder] Successfully synced notes to Hoarder");
          setTimeout(async () => {
            try {
              const { currentNotes: latestNotes, originalNotes: currentOriginalNotes } = await this.extractNotesFromFile(file.path);
              if (latestNotes === currentNotesStr && currentOriginalNotes !== currentNotesStr) {
                await this.app.fileManager.processFrontMatter(file, (frontmatter) => {
                  frontmatter["original_note"] = currentNotesStr;
                });
                console.debug("[Hoarder] Updated original_note in frontmatter");
              } else if (latestNotes !== currentNotesStr) {
                console.debug("[Hoarder] Notes changed again, skipping frontmatter update");
              } else {
                console.debug(
                  "[Hoarder] original_note already up to date, skipping frontmatter update"
                );
              }
            } catch (error) {
              console.error("[Hoarder] Error updating frontmatter:", error);
            }
          }, 5e3);
          new import_obsidian3.Notice("Notes synced to Hoarder");
        } else {
          console.error("[Hoarder] Failed to update bookmark in Hoarder");
          new import_obsidian3.Notice("Failed to sync notes to Hoarder");
        }
      } else {
        console.log("[Hoarder] Notes unchanged, no sync needed");
      }
    } catch (error) {
      console.error("[Hoarder] Error handling file modification:", error);
      new import_obsidian3.Notice("Failed to sync notes to Hoarder");
    }
  }
  initializeClient() {
    if (!this.settings.apiKey || !this.settings.apiEndpoint) {
      this.client = null;
    } else {
      this.client = new HoarderApiClient(
        this.settings.apiEndpoint,
        this.settings.apiKey,
        this.settings.useObsidianRequest
      );
    }
  }
};

/* nosourcemap */