diff --git a/docs/superpowers/plans/2026-04-14-i18n-en-de.md b/docs/superpowers/plans/2026-04-14-i18n-en-de.md new file mode 100644 index 0000000..05fb8af --- /dev/null +++ b/docs/superpowers/plans/2026-04-14-i18n-en-de.md @@ -0,0 +1,526 @@ +# i18n (en + de) Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add English and German translations to obsidian-micropub by extracting all hardcoded UI strings into locale files and wiring them through a tiny `t()` helper. + +**Architecture:** A `src/i18n.ts` module exports a single `t(key, vars?)` function that reads `window.moment.locale()` (Obsidian's own locale) at call time, looks up the key in the matching locale map (falling back to `en`), and does `{var}` substitution. Locale maps live in `src/lang/en.ts` and `src/lang/de.ts`. No external dependencies, no build changes. + +**Tech Stack:** TypeScript, Obsidian API (`window.moment.locale()`), esbuild (existing) + +--- + +## File Map + +| Action | Path | Responsibility | +|--------|------|---------------| +| Create | `src/lang/en.ts` | English string map (source of truth) | +| Create | `src/lang/de.ts` | German string map | +| Create | `src/i18n.ts` | `t()` helper — locale detection + lookup + interpolation | +| Modify | `src/main.ts` | Replace hardcoded strings with `t()` calls | +| Modify | `src/SettingsTab.ts` | Replace hardcoded strings with `t()` calls | +| Modify | `src/SyndicationDialog.ts` | Replace hardcoded strings with `t()` calls | +| Modify | `src/IndieAuth.ts` | Replace one hardcoded error string with `t()` call | +| Modify | `README.md` | Note i18n support under Configuration | + +--- + +## Task 1: English locale file + +**Files:** +- Create: `src/lang/en.ts` + +- [ ] **Step 1: Create `src/lang/en.ts` with all UI strings** + +```typescript +// src/lang/en.ts +export const en: Record = { + // Commands & ribbon + cmdPublish: "Publish to Micropub", + cmdUpdate: "Update existing Micropub post", + + // Notices — main.ts + noticeOpenNote: "Open a Markdown note to publish.", + noticeNoEndpoint: "⚠️ Micropub endpoint not configured. Open plugin settings to add it.", + noticeNoToken: "⚠️ Access token not configured. Open plugin settings to add it.", + noticePublishing: "Publishing…", + noticePublished: "✅ Published!", + noticePublishFailed: "❌ Publish failed: {error}", + noticeError: "❌ Error: {error}", + noticeNoSyndTargets: "⚠️ Could not fetch syndication targets. Publishing without dialog.", + + // Settings headings + settingsTitle: "Micropub Publisher", + settingsAccount: "Account", + settingsEndpoints: "Endpoints", + settingsEndpointsHint: "These are filled automatically when you sign in. Only edit them manually if your server uses non-standard paths.", + settingsPublishBehaviour:"Publish Behaviour", + settingsDigitalGarden: "Digital Garden", + + // Settings — endpoints + settingMicropubEndpoint: "Micropub endpoint", + settingMicropubEndpointDesc: "e.g. https://example.com/micropub", // intentional: replaces personal domain in source + settingMediaEndpoint: "Media endpoint", + settingMediaEndpointDesc:"For image uploads. Auto-discovered if blank.", + + // Settings — publish behaviour + settingVisibility: "Default visibility", + settingVisibilityDesc: "Applies when the note has no explicit visibility property.", + visibilityPublic: "Public", + visibilityUnlisted: "Unlisted", + visibilityPrivate: "Private", + + settingWriteUrl: "Write URL back to note", + settingWriteUrlDesc: "After publishing, store the post URL as `mp-url` in frontmatter. Subsequent publishes will update the existing post instead of creating a new one.", + + settingSyndDialog: "Syndication dialog", + settingSyndDialogDesc: "When to show the cross-posting dialog before publishing. 'When needed' shows it only if the note has no mp-syndicate-to frontmatter.", + syndDialogWhenNeeded: "When needed", + syndDialogAlways: "Always", + syndDialogNever: "Never", + + settingSyndDefaults: "Default syndication targets", + settingSyndDefaultsNone: "None configured. Targets checked by default in the publish dialog.", + btnClearDefaults: "Clear defaults", + + // Settings — digital garden + settingGardenTags: "Map #garden/* tags to gardenStage", + settingGardenTagsDesc: "Obsidian tags like #garden/plant become a `garden-stage: plant` Micropub property. The blog renders these as growth stage badges at /garden/.", + settingGardenStages: "Stages: plant 🌱 · cultivate 🌿 · question ❓ · repot 🪴 · revitalize ✨ · revisit 🔄", + + // Settings — sign-in / sign-out + settingSiteUrl: "Site URL", + settingSiteUrlDesc: "Your site's home page. Clicking Sign in opens your blog's login page in the browser — the same flow iA Writer uses.", + settingSiteUrlPlaceholder: "https://example.com", // intentional: replaces personal domain in source + btnSignIn: "Sign in", + btnOpeningBrowser: "Opening browser…", + noticeEnterSiteUrl: "Enter your site URL first.", + noticeSignedInAs: "✅ Signed in as {me}", + noticeSignInFailed: "Sign-in failed: {error}", + lblSignedIn: "Signed in", + btnSignOut: "Sign out", + manualTokenSummary: "Or paste a token manually", + settingAccessToken: "Access token", + settingAccessTokenDesc: "Bearer token from your Indiekit admin panel.", + btnVerify: "Verify", + noticeSetEndpointFirst: "Set the Micropub endpoint and token first.", + noticeTokenValid: "✅ Token is valid!", + noticeTokenCheckFailed: "Token check failed: {error}", + + // Syndication dialog + syndDialogTitle: "Syndication targets", + syndDialogSubtitle: "Choose where to cross-post this note.", + btnCancel: "Cancel", + btnPublish: "Publish", + + // IndieAuth + errSignInTimeout: "Sign-in timed out (5 min). Please try again.", +}; +``` + +- [ ] **Step 2: Build to confirm no TypeScript errors** + +```bash +cd "/Users/sven/PARA/1. Projects/obsidian-micropub" && npm run build +``` +Expected: exits 0. + +- [ ] **Step 3: Commit** + +```bash +cd "/Users/sven/PARA/1. Projects/obsidian-micropub" +git add src/lang/en.ts +git commit -m "feat(i18n): add English locale file with all UI strings" +``` + +--- + +## Task 2: German locale file + +**Files:** +- Create: `src/lang/de.ts` + +- [ ] **Step 1: Create `src/lang/de.ts`** + +```typescript +// src/lang/de.ts +export const de: Record = { + // Commands & ribbon + cmdPublish: "An Micropub veröffentlichen", + cmdUpdate: "Bestehenden Micropub-Beitrag aktualisieren", + + // Notices — main.ts + noticeOpenNote: "Öffne eine Markdown-Notiz zum Veröffentlichen.", + noticeNoEndpoint: "⚠️ Micropub-Endpunkt nicht konfiguriert. Bitte in den Plugin-Einstellungen eintragen.", + noticeNoToken: "⚠️ Zugriffstoken nicht konfiguriert. Bitte in den Plugin-Einstellungen eintragen.", + noticePublishing: "Wird veröffentlicht…", + noticePublished: "✅ Veröffentlicht!", + noticePublishFailed: "❌ Veröffentlichung fehlgeschlagen: {error}", + noticeError: "❌ Fehler: {error}", + noticeNoSyndTargets: "⚠️ Syndizierungsziele konnten nicht abgerufen werden. Veröffentlichung ohne Dialog.", + + // Settings headings + settingsTitle: "Micropub Publisher", + settingsAccount: "Konto", + settingsEndpoints: "Endpunkte", + settingsEndpointsHint: "Diese werden beim Anmelden automatisch ausgefüllt. Nur manuell bearbeiten, wenn der Server nicht standardmäßige Pfade verwendet.", + settingsPublishBehaviour:"Veröffentlichungsverhalten", + settingsDigitalGarden: "Digitaler Garten", + + // Settings — endpoints + settingMicropubEndpoint: "Micropub-Endpunkt", + settingMicropubEndpointDesc: "z. B. https://example.com/micropub", + settingMediaEndpoint: "Medien-Endpunkt", + settingMediaEndpointDesc:"Für Bild-Uploads. Wird automatisch ermittelt, wenn leer.", + + // Settings — publish behaviour + settingVisibility: "Standard-Sichtbarkeit", + settingVisibilityDesc: "Gilt, wenn die Notiz keine explizite Sichtbarkeits-Eigenschaft hat.", + visibilityPublic: "Öffentlich", + visibilityUnlisted: "Nicht gelistet", + visibilityPrivate: "Privat", + + settingWriteUrl: "URL zurück in Notiz schreiben", + settingWriteUrlDesc: "Nach der Veröffentlichung wird die Beitrags-URL als `mp-url` im Frontmatter gespeichert. Spätere Veröffentlichungen aktualisieren den Beitrag statt einen neuen zu erstellen.", + + settingSyndDialog: "Syndizierungsdialog", + settingSyndDialogDesc: "Wann der Dialog zum Querverweis vor der Veröffentlichung angezeigt wird. 'Bei Bedarf' zeigt ihn nur, wenn kein mp-syndicate-to im Frontmatter vorhanden ist.", + syndDialogWhenNeeded: "Bei Bedarf", + syndDialogAlways: "Immer", + syndDialogNever: "Nie", + + settingSyndDefaults: "Standard-Syndizierungsziele", + settingSyndDefaultsNone: "Keine konfiguriert. Im Veröffentlichungsdialog standardmäßig aktivierte Ziele.", + btnClearDefaults: "Standards löschen", + + // Settings — digital garden + settingGardenTags: "#garden/*-Tags zu gardenStage zuordnen", + settingGardenTagsDesc: "Obsidian-Tags wie #garden/plant werden zur Micropub-Eigenschaft `garden-stage: plant`. Der Blog zeigt diese als Wachstumsstufen-Abzeichen unter /garden/ an.", + settingGardenStages: "Stufen: plant 🌱 · cultivate 🌿 · question ❓ · repot 🪴 · revitalize ✨ · revisit 🔄", + + // Settings — sign-in / sign-out + settingSiteUrl: "Website-URL", + settingSiteUrlDesc: "Startseite deiner Website. Klick auf Anmelden öffnet die Login-Seite deines Blogs im Browser.", + settingSiteUrlPlaceholder: "https://example.com", + btnSignIn: "Anmelden", + btnOpeningBrowser: "Browser wird geöffnet…", + noticeEnterSiteUrl: "Bitte zuerst die Website-URL eingeben.", + noticeSignedInAs: "✅ Angemeldet als {me}", + noticeSignInFailed: "Anmeldung fehlgeschlagen: {error}", + lblSignedIn: "Angemeldet", + btnSignOut: "Abmelden", + manualTokenSummary: "Oder Token manuell einfügen", + settingAccessToken: "Zugriffstoken", + settingAccessTokenDesc: "Bearer-Token aus deinem Indiekit-Adminbereich.", + btnVerify: "Prüfen", + noticeSetEndpointFirst: "Bitte zuerst Micropub-Endpunkt und Token eingeben.", + noticeTokenValid: "✅ Token ist gültig!", + noticeTokenCheckFailed: "Token-Prüfung fehlgeschlagen: {error}", + + // Syndication dialog + syndDialogTitle: "Syndizierungsziele", + syndDialogSubtitle: "Wo soll diese Notiz gleichzeitig veröffentlicht werden?", + btnCancel: "Abbrechen", + btnPublish: "Veröffentlichen", + + // IndieAuth + errSignInTimeout: "Anmeldung abgelaufen (5 Min.). Bitte erneut versuchen.", +}; +``` + +- [ ] **Step 2: Build** + +```bash +cd "/Users/sven/PARA/1. Projects/obsidian-micropub" && npm run build +``` +Expected: exits 0. + +- [ ] **Step 3: Commit** + +```bash +cd "/Users/sven/PARA/1. Projects/obsidian-micropub" +git add src/lang/de.ts +git commit -m "feat(i18n): add German locale file" +``` + +--- + +## Task 3: i18n helper + +**Files:** +- Create: `src/i18n.ts` + +- [ ] **Step 1: Create `src/i18n.ts`** + +```typescript +// src/i18n.ts +import { en } from "./lang/en"; +import { de } from "./lang/de"; + +const locales: Record> = { en, de }; + +/** + * Returns the translated string for `key` in the active Obsidian locale. + * Falls back to English if the locale or key is missing. + * + * Supports `{var}` interpolation: + * t("noticePublishFailed", { error: "500" }) + * → "❌ Publish failed: 500" + */ +export function t(key: string, vars?: Record): string { + const lang = (window.moment?.locale() ?? "en").split("-")[0]; + const map = locales[lang] ?? locales["en"]; + let str = map[key] ?? locales["en"][key] ?? key; + + if (vars) { + for (const [k, v] of Object.entries(vars)) { + str = str.split(`{${k}}`).join(v); // replaceAll not available in ES2018 + } + } + return str; +} +``` + +- [ ] **Step 2: Build** + +```bash +cd "/Users/sven/PARA/1. Projects/obsidian-micropub" && npm run build +``` +Expected: exits 0. + +- [ ] **Step 3: Commit** + +```bash +cd "/Users/sven/PARA/1. Projects/obsidian-micropub" +git add src/i18n.ts +git commit -m "feat(i18n): add t() helper with locale detection and interpolation" +``` + +--- + +## Task 4: Wire `t()` into `main.ts` + +**Files:** +- Modify: `src/main.ts` + +- [ ] **Step 1: Add import and replace all strings** + +At the top of `main.ts`, add: +```typescript +import { t } from "./i18n"; +``` + +Then replace each hardcoded string: + +| Old | New | +|-----|-----| +| `"Publish to Micropub"` (command name, line 36) | `t("cmdPublish")` | +| `"Update existing Micropub post"` (line 49) | `t("cmdUpdate")` | +| `"Publish to Micropub"` (ribbon tooltip, line 75) | `t("cmdPublish")` | +| `"Open a Markdown note to publish."` (line 78) | `t("noticeOpenNote")` | +| `"⚠️ Micropub endpoint not configured…"` (line 93) | `t("noticeNoEndpoint")` | +| `"⚠️ Access token not configured…"` (line 100) | `t("noticeNoToken")` | +| `"Publishing…"` (line 114) | `t("noticePublishing")` | +| `` `✅ Published!${urlDisplay}` `` (line 126) | `` `${t("noticePublished")}${urlDisplay}` `` | +| `` `❌ Publish failed: ${result.error}` `` (line 128) | `t("noticePublishFailed", { error: result.error ?? "" })` | +| `` `❌ Error: ${msg}` `` (line 134) | `t("noticeError", { error: msg })` | +| `"⚠️ Could not fetch syndication targets…"` (line 167) | `t("noticeNoSyndTargets")` | + +- [ ] **Step 2: Build** + +```bash +cd "/Users/sven/PARA/1. Projects/obsidian-micropub" && npm run build +``` +Expected: exits 0. + +- [ ] **Step 3: Commit** + +```bash +cd "/Users/sven/PARA/1. Projects/obsidian-micropub" +git add src/main.ts +git commit -m "feat(i18n): wire t() into main.ts" +``` + +--- + +## Task 5: Wire `t()` into `SettingsTab.ts` + +**Files:** +- Modify: `src/SettingsTab.ts` + +- [ ] **Step 1: Add import** + +```typescript +import { t } from "./i18n"; +``` + +- [ ] **Step 2: Replace strings in `display()`** + +| Old | New | +|-----|-----| +| `"Micropub Publisher"` (h2) | `t("settingsTitle")` | +| `"Account"` (h3) | `t("settingsAccount")` | +| `"Endpoints"` (h3) | `t("settingsEndpoints")` | +| `"These are filled automatically…"` (p) | `t("settingsEndpointsHint")` | +| `"Micropub endpoint"` (setName) | `t("settingMicropubEndpoint")` | +| `"e.g. https://blog.giersig.eu/micropub"` (setDesc) | `t("settingMicropubEndpointDesc")` | +| `"Media endpoint"` (setName) | `t("settingMediaEndpoint")` | +| `"For image uploads…"` (setDesc) | `t("settingMediaEndpointDesc")` | +| `"Publish Behaviour"` (h3) | `t("settingsPublishBehaviour")` | +| `"Default visibility"` (setName) | `t("settingVisibility")` | +| `"Applies when the note…"` (setDesc) | `t("settingVisibilityDesc")` | +| `"Public"` | `t("visibilityPublic")` | +| `"Unlisted"` | `t("visibilityUnlisted")` | +| `"Private"` | `t("visibilityPrivate")` | +| `"Write URL back to note"` (setName) | `t("settingWriteUrl")` | +| `"After publishing, store…"` (setDesc) | `t("settingWriteUrlDesc")` | +| `"Syndication dialog"` (setName) | `t("settingSyndDialog")` | +| `"When to show the cross-posting…"` (setDesc) | `t("settingSyndDialogDesc")` | +| `"When needed"` | `t("syndDialogWhenNeeded")` | +| `"Always"` | `t("syndDialogAlways")` | +| `"Never"` | `t("syndDialogNever")` | +| `"Default syndication targets"` (setName) | `t("settingSyndDefaults")` | +| `"None configured…"` (false branch of setDesc — when `defaults.length === 0`) | `t("settingSyndDefaultsNone")` | +| `"Clear defaults"` (btn) | `t("btnClearDefaults")` | +| `"Digital Garden"` (h3) | `t("settingsDigitalGarden")` | +| `"Map #garden/* tags…"` (setName) | `t("settingGardenTags")` | +| `"Obsidian tags like…"` (setDesc) | `t("settingGardenTagsDesc")` | +| `"Stages: plant 🌱…"` (p) | `t("settingGardenStages")` | + +- [ ] **Step 3: Replace strings in `renderSignedOut()`** + +| Old | New | +|-----|-----| +| `"Site URL"` (setName) | `t("settingSiteUrl")` | +| `"Your site's home page…"` (setDesc) | `t("settingSiteUrlDesc")` | +| `"https://blog.giersig.eu"` (placeholder) | `t("settingSiteUrlPlaceholder")` | +| `"Sign in"` (btn) | `t("btnSignIn")` | +| `"Enter your site URL first."` (Notice) | `t("noticeEnterSiteUrl")` | +| `"Opening browser…"` (btn) | `t("btnOpeningBrowser")` | +| `` `✅ Signed in as ${result.me}` `` | `t("noticeSignedInAs", { me: result.me })` | +| `` `Sign-in failed: ${String(err)}` `` | `t("noticeSignInFailed", { error: String(err) })` | +| `"Sign in"` (catch btn reset) | `t("btnSignIn")` | +| `"Or paste a token manually"` (summary) | `t("manualTokenSummary")` | +| `"Access token"` (setName) | `t("settingAccessToken")` | +| `"Bearer token from your Indiekit admin panel."` (setDesc) | `t("settingAccessTokenDesc")` | +| `"Verify"` (btn) | `t("btnVerify")` | +| `"Set the Micropub endpoint and token first."` (Notice) | `t("noticeSetEndpointFirst")` | +| `"✅ Token is valid!"` (Notice) | `t("noticeTokenValid")` | +| `` `Token check failed: ${String(err)}` `` | `t("noticeTokenCheckFailed", { error: String(err) })` | + +- [ ] **Step 4: Replace strings in `renderSignedIn()`** + +| Old | New | +|-----|-----| +| `"Signed in"` (label) | `t("lblSignedIn")` | +| `"Site URL"` (setName) | `t("settingSiteUrl")` | +| `"Sign out"` (btn) | `t("btnSignOut")` | + +- [ ] **Step 5: Build** + +```bash +cd "/Users/sven/PARA/1. Projects/obsidian-micropub" && npm run build +``` +Expected: exits 0. + +- [ ] **Step 6: Commit** + +```bash +cd "/Users/sven/PARA/1. Projects/obsidian-micropub" +git add src/SettingsTab.ts +git commit -m "feat(i18n): wire t() into SettingsTab.ts" +``` + +--- + +## Task 6: Wire `t()` into `SyndicationDialog.ts` and `IndieAuth.ts` + +**Files:** +- Modify: `src/SyndicationDialog.ts` +- Modify: `src/IndieAuth.ts` + +- [ ] **Step 1: Update `SyndicationDialog.ts`** + +Add import: +```typescript +import { t } from "./i18n"; +``` + +Replace: + +| Old | New | +|-----|-----| +| `"Syndication targets"` (h2) | `t("syndDialogTitle")` | +| `"Choose where to cross-post this note."` (p) | `t("syndDialogSubtitle")` | +| `"Cancel"` (btn) | `t("btnCancel")` | +| `"Publish"` (btn) | `t("btnPublish")` | + +- [ ] **Step 2: Update `IndieAuth.ts`** + +Add import at top: +```typescript +import { t } from "./i18n"; +``` + +Replace: + +| Old | New | +|-----|-----| +| `"Sign-in timed out (5 min). Please try again."` | `t("errSignInTimeout")` | + +- [ ] **Step 3: Build** + +```bash +cd "/Users/sven/PARA/1. Projects/obsidian-micropub" && npm run build +``` +Expected: exits 0. + +- [ ] **Step 4: Commit** + +```bash +cd "/Users/sven/PARA/1. Projects/obsidian-micropub" +git add src/SyndicationDialog.ts src/IndieAuth.ts +git commit -m "feat(i18n): wire t() into SyndicationDialog and IndieAuth" +``` + +--- + +## Task 7: README update + +**Files:** +- Modify: `README.md` + +- [ ] **Step 1: Add i18n note to Configuration section** + +After the Settings reference table, add: + +```markdown +### Language + +The plugin follows Obsidian's display language. Set it in **Settings → About → Language**. Currently supported: English (`en`), German (`de`). +``` + +- [ ] **Step 2: Add `SyndicationDialog.ts` already done in prior session (verify)** + +Architecture table should already show `SyndicationDialog.ts` from the prior README update. + +- [ ] **Step 3: Commit** + +```bash +cd "/Users/sven/PARA/1. Projects/obsidian-micropub" +git add README.md +git commit -m "docs: note i18n support (en/de) in README" +``` + +--- + +## Manual verification checklist + +After all tasks, load the plugin in Obsidian and verify: + +- [ ] With Obsidian in English: all UI labels, notices, and dialog text appear in English +- [ ] With Obsidian in German (`de`): all UI labels, notices, and dialog text appear in German +- [ ] Publishing a note shows the correct locale notice (`Publishing…` / `Wird veröffentlicht…`) +- [ ] The syndication dialog title and buttons are translated +- [ ] Sign-in flow button text and error notices are translated +- [ ] Switching locale and restarting Obsidian picks up the new language diff --git a/main.js b/main.js index ff1e599..242a4e9 100644 --- a/main.js +++ b/main.js @@ -3,19 +3,19 @@ THIS IS A GENERATED/BUNDLED FILE BY ESBUILD if you want to view the source, please visit the github repository of this plugin */ -"use strict";var xt=Object.create;var U=Object.defineProperty;var Pt=Object.getOwnPropertyDescriptor;var Rt=Object.getOwnPropertyNames;var At=Object.getPrototypeOf,Ct=Object.prototype.hasOwnProperty;var $t=(g,t)=>{for(var e in t)U(g,e,{get:t[e],enumerable:!0})},bt=(g,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of Rt(t))!Ct.call(g,i)&&i!==e&&U(g,i,{get:()=>t[i],enumerable:!(n=Pt(t,i))||n.enumerable});return g};var Dt=(g,t,e)=>(e=g!=null?xt(At(g)):{},bt(t||!g||!g.__esModule?U(e,"default",{value:g,enumerable:!0}):e,g)),Mt=g=>bt(U({},"__esModule",{value:!0}),g);var Ft={};$t(Ft,{default:()=>O});module.exports=Mt(Ft);var y=require("obsidian");var wt={micropubEndpoint:"",mediaEndpoint:"",accessToken:"",defaultSyndicateTo:[],autoDiscover:!1,siteUrl:"",authorizationEndpoint:"",tokenEndpoint:"",me:"",writeUrlToFrontmatter:!0,mapGardenTags:!0,defaultVisibility:"public",showSyndicationDialog:"when-needed"};var h=require("obsidian");var k=require("obsidian"),b=class{constructor(t,e,n){this.getEndpoint=t;this.getMediaEndpoint=e;this.getToken=n}async fetchConfig(){let t=`${this.getEndpoint()}?q=config`;return(await(0,k.requestUrl)({url:t,method:"GET",headers:this.authHeaders()})).json}async discoverEndpoints(t){let n=(await(0,k.requestUrl)({url:t,method:"GET"})).text,i=this.extractLinkRel(n,"micropub"),s=this.extractLinkRel(n,"token_endpoint"),r;if(i)try{r=(await this.fetchConfigFrom(i))["media-endpoint"]}catch(o){}return{micropubEndpoint:i,tokenEndpoint:s,mediaEndpoint:r}}async createPost(t){var n,i,s;let e={type:["h-entry"],properties:t};try{let r=await(0,k.requestUrl)({url:this.getEndpoint(),method:"POST",headers:{...this.authHeaders(),"Content-Type":"application/json"},body:JSON.stringify(e),throw:!1});if(r.status===201||r.status===202)return{success:!0,url:((n=r.headers)==null?void 0:n.location)||((i=r.headers)==null?void 0:i.Location)||((s=r.json)==null?void 0:s.url)};let o=this.extractError(r.text);return{success:!1,error:`HTTP ${r.status}: ${o}`}}catch(r){return{success:!1,error:String(r)}}}async updatePost(t,e){let n={action:"update",url:t,replace:e};try{let i=await(0,k.requestUrl)({url:this.getEndpoint(),method:"POST",headers:{...this.authHeaders(),"Content-Type":"application/json"},body:JSON.stringify(n),throw:!1});return i.status>=200&&i.status<300?{success:!0,url:t}:{success:!1,error:`HTTP ${i.status}: ${this.extractError(i.text)}`}}catch(i){return{success:!1,error:String(i)}}}async uploadMedia(t,e,n){var u,f,T;let i=this.getMediaEndpoint()||`${this.getEndpoint()}/media`,s=`----MicropubBoundary${Date.now()}`,r=`--${s}\r +"use strict";var Mt=Object.create;var B=Object.defineProperty;var Ut=Object.getOwnPropertyDescriptor;var Rt=Object.getOwnPropertyNames;var Ct=Object.getPrototypeOf,Nt=Object.prototype.hasOwnProperty;var Bt=(g,t)=>{for(var e in t)B(g,e,{get:t[e],enumerable:!0})},St=(g,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of Rt(t))!Nt.call(g,i)&&i!==e&&B(g,i,{get:()=>t[i],enumerable:!(n=Ut(t,i))||n.enumerable});return g};var Ft=(g,t,e)=>(e=g!=null?Mt(Ct(g)):{},St(t||!g||!g.__esModule?B(e,"default",{value:g,enumerable:!0}):e,g)),$t=g=>St(B({},"__esModule",{value:!0}),g);var Ot={};Bt(Ot,{default:()=>I});module.exports=$t(Ot);var y=require("obsidian");var vt={micropubEndpoint:"",mediaEndpoint:"",accessToken:"",defaultSyndicateTo:[],autoDiscover:!1,siteUrl:"",authorizationEndpoint:"",tokenEndpoint:"",me:"",writeUrlToFrontmatter:!0,mapGardenTags:!0,defaultVisibility:"public",showSyndicationDialog:"when-needed"};var m=require("obsidian");var T=require("obsidian"),w=class{constructor(t,e,n){this.getEndpoint=t;this.getMediaEndpoint=e;this.getToken=n}async fetchConfig(){let t=`${this.getEndpoint()}?q=config`;return(await(0,T.requestUrl)({url:t,method:"GET",headers:this.authHeaders()})).json}async discoverEndpoints(t){let n=(await(0,T.requestUrl)({url:t,method:"GET"})).text,i=this.extractLinkRel(n,"micropub"),s=this.extractLinkRel(n,"token_endpoint"),r;if(i)try{r=(await this.fetchConfigFrom(i))["media-endpoint"]}catch(a){}return{micropubEndpoint:i,tokenEndpoint:s,mediaEndpoint:r}}async createPost(t){var n,i,s;let e={type:["h-entry"],properties:t};try{let r=await(0,T.requestUrl)({url:this.getEndpoint(),method:"POST",headers:{...this.authHeaders(),"Content-Type":"application/json"},body:JSON.stringify(e),throw:!1});if(r.status===201||r.status===202)return{success:!0,url:((n=r.headers)==null?void 0:n.location)||((i=r.headers)==null?void 0:i.Location)||((s=r.json)==null?void 0:s.url)};let a=this.extractError(r.text);return{success:!1,error:`HTTP ${r.status}: ${a}`}}catch(r){return{success:!1,error:String(r)}}}async updatePost(t,e){let n={action:"update",url:t,replace:e};try{let i=await(0,T.requestUrl)({url:this.getEndpoint(),method:"POST",headers:{...this.authHeaders(),"Content-Type":"application/json"},body:JSON.stringify(n),throw:!1});return i.status>=200&&i.status<300?{success:!0,url:t}:{success:!1,error:`HTTP ${i.status}: ${this.extractError(i.text)}`}}catch(i){return{success:!1,error:String(i)}}}async uploadMedia(t,e,n){var h,b,E;let i=this.getMediaEndpoint()||`${this.getEndpoint()}/media`,s=`----MicropubBoundary${Date.now()}`,r=`--${s}\r Content-Disposition: form-data; name="file"; filename="${e}"\r Content-Type: ${n}\r \r -`,o=`\r +`,a=`\r --${s}--\r -`,a=new TextEncoder().encode(r),c=new TextEncoder().encode(o),l=new Uint8Array(t),d=new Uint8Array(a.length+l.length+c.length);d.set(a,0),d.set(l,a.length),d.set(c,a.length+l.length);let p=await(0,k.requestUrl)({url:i,method:"POST",headers:{...this.authHeaders(),"Content-Type":`multipart/form-data; boundary=${s}`},body:d.buffer,throw:!1});if(p.status===201||p.status===202){let w=((u=p.headers)==null?void 0:u.location)||((f=p.headers)==null?void 0:f.Location)||((T=p.json)==null?void 0:T.url);if(w)return w}throw new Error(`Media upload failed (HTTP ${p.status}): ${this.extractError(p.text)}`)}authHeaders(){return{Authorization:`Bearer ${this.getToken()}`}}extractLinkRel(t,e){var s;let n=new RegExp(`]+rel=["']${e}["'][^>]+href=["']([^"']+)["']|]+href=["']([^"']+)["'][^>]+rel=["']${e}["']`,"i"),i=t.match(n);return(s=i==null?void 0:i[1])!=null?s:i==null?void 0:i[2]}async fetchConfigFrom(t){return(await(0,k.requestUrl)({url:`${t}?q=config`,method:"GET",headers:this.authHeaders()})).json}extractError(t){var e,n;try{let i=JSON.parse(t);return(n=(e=i.error_description)!=null?e:i.error)!=null?n:t.slice(0,200)}catch(i){return t.slice(0,200)}}};var A=Dt(require("crypto")),j=require("obsidian"),vt="https://svemagie.github.io/obsidian-micropub/",St="https://svemagie.github.io/obsidian-micropub/callback",kt="create update media",Ut=300*1e3,R=null;function Tt(g){if(!R)return;let{resolve:t,state:e}=R;R=null,t(g)}var F=class g{static async discoverEndpoints(t){let n=(await(0,j.requestUrl)({url:t,method:"GET"})).text,i=g.extractLinkRel(n,"authorization_endpoint"),s=g.extractLinkRel(n,"token_endpoint"),r=g.extractLinkRel(n,"micropub");if(!i)throw new Error(`No found at ${t}. Make sure Indiekit is running and SITE_URL is set correctly.`);if(!s)throw new Error(`No found at ${t}.`);return{authorizationEndpoint:i,tokenEndpoint:s,micropubEndpoint:r}}static async signIn(t){var f,T,w,E,$,x;let{authorizationEndpoint:e,tokenEndpoint:n,micropubEndpoint:i}=await g.discoverEndpoints(t),s=g.base64url(A.randomBytes(16)),r=g.base64url(A.randomBytes(64)),o=g.base64url(A.createHash("sha256").update(r).digest()),a=new Promise((D,P)=>{let v=setTimeout(()=>{R=null,P(new Error("Sign-in timed out (5 min). Please try again."))},Ut);R={state:s,resolve:M=>{clearTimeout(v),D(M)}}}),c=new URL(e);c.searchParams.set("response_type","code"),c.searchParams.set("client_id",vt),c.searchParams.set("redirect_uri",St),c.searchParams.set("state",s),c.searchParams.set("code_challenge",o),c.searchParams.set("code_challenge_method","S256"),c.searchParams.set("scope",kt),c.searchParams.set("me",t),window.open(c.toString());let l=await a;if(l.state!==s)throw new Error("State mismatch \u2014 possible CSRF attack. Please try again.");let d=l.code;if(!d)throw new Error((T=(f=l.error_description)!=null?f:l.error)!=null?T:"No authorization code received.");let p=await(0,j.requestUrl)({url:n,method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},body:new URLSearchParams({grant_type:"authorization_code",code:d,client_id:vt,redirect_uri:St,code_verifier:r}).toString(),throw:!1}),u=p.json;if(!u.access_token)throw new Error((E=(w=u.error_description)!=null?w:u.error)!=null?E:`Token exchange failed (HTTP ${p.status})`);return{accessToken:u.access_token,scope:($=u.scope)!=null?$:kt,me:(x=u.me)!=null?x:t,authorizationEndpoint:e,tokenEndpoint:n,micropubEndpoint:i}}static base64url(t){return t.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}static extractLinkRel(t,e){var s;let n=new RegExp(`]+rel=["'][^"']*\\b${e}\\b[^"']*["'][^>]+href=["']([^"']+)["']|]+href=["']([^"']+)["'][^>]+rel=["'][^"']*\\b${e}\\b[^"']*["']`,"i"),i=t.match(n);return(s=i==null?void 0:i[1])!=null?s:i==null?void 0:i[2]}};var L=class extends h.PluginSettingTab{constructor(e,n){super(e,n);this.plugin=n}display(){let{containerEl:e}=this;e.empty(),e.createEl("h2",{text:"Micropub Publisher"}),e.createEl("h3",{text:"Account"}),this.plugin.settings.me&&this.plugin.settings.accessToken?this.renderSignedIn(e):this.renderSignedOut(e),e.createEl("h3",{text:"Endpoints"}),e.createEl("p",{text:"These are filled automatically when you sign in. Only edit them manually if your server uses non-standard paths.",cls:"setting-item-description"}),new h.Setting(e).setName("Micropub endpoint").setDesc("e.g. https://blog.giersig.eu/micropub").addText(s=>s.setPlaceholder("https://example.com/micropub").setValue(this.plugin.settings.micropubEndpoint).onChange(async r=>{this.plugin.settings.micropubEndpoint=r.trim(),await this.plugin.saveSettings()})),new h.Setting(e).setName("Media endpoint").setDesc("For image uploads. Auto-discovered if blank.").addText(s=>s.setPlaceholder("https://example.com/micropub/media").setValue(this.plugin.settings.mediaEndpoint).onChange(async r=>{this.plugin.settings.mediaEndpoint=r.trim(),await this.plugin.saveSettings()})),e.createEl("h3",{text:"Publish Behaviour"}),new h.Setting(e).setName("Default visibility").setDesc("Applies when the note has no explicit visibility property.").addDropdown(s=>s.addOption("public","Public").addOption("unlisted","Unlisted").addOption("private","Private").setValue(this.plugin.settings.defaultVisibility).onChange(async r=>{this.plugin.settings.defaultVisibility=r,await this.plugin.saveSettings()})),new h.Setting(e).setName("Write URL back to note").setDesc("After publishing, store the post URL as `mp-url` in frontmatter. Subsequent publishes will update the existing post instead of creating a new one.").addToggle(s=>s.setValue(this.plugin.settings.writeUrlToFrontmatter).onChange(async r=>{this.plugin.settings.writeUrlToFrontmatter=r,await this.plugin.saveSettings()})),new h.Setting(e).setName("Syndication dialog").setDesc("When to show the cross-posting dialog before publishing. 'When needed' shows it only if the note has no mp-syndicate-to frontmatter.").addDropdown(s=>s.addOption("when-needed","When needed").addOption("always","Always").addOption("never","Never").setValue(this.plugin.settings.showSyndicationDialog).onChange(async r=>{this.plugin.settings.showSyndicationDialog=r,await this.plugin.saveSettings()}));let n=this.plugin.settings.defaultSyndicateTo,i=new h.Setting(e).setName("Default syndication targets").setDesc(n.length>0?n.join(", "):"None configured. Targets checked by default in the publish dialog.");n.length>0&&i.addButton(s=>s.setButtonText("Clear defaults").setWarning().onClick(async()=>{this.plugin.settings.defaultSyndicateTo=[],await this.plugin.saveSettings(),this.display()})),e.createEl("h3",{text:"Digital Garden"}),new h.Setting(e).setName("Map #garden/* tags to gardenStage").setDesc("Obsidian tags like #garden/plant become a `garden-stage: plant` Micropub property. The blog renders these as growth stage badges at /garden/.").addToggle(s=>s.setValue(this.plugin.settings.mapGardenTags).onChange(async r=>{this.plugin.settings.mapGardenTags=r,await this.plugin.saveSettings()})),e.createEl("p",{text:"Stages: plant \u{1F331} \xB7 cultivate \u{1F33F} \xB7 question \u2753 \xB7 repot \u{1FAB4} \xB7 revitalize \u2728 \xB7 revisit \u{1F504}",cls:"setting-item-description"})}renderSignedOut(e){new h.Setting(e).setName("Site URL").setDesc("Your site's home page. Clicking Sign in opens your blog's login page in the browser \u2014 the same flow iA Writer uses.").addText(i=>i.setPlaceholder("https://blog.giersig.eu").setValue(this.plugin.settings.siteUrl).onChange(async s=>{this.plugin.settings.siteUrl=s.trim(),await this.plugin.saveSettings()})).addButton(i=>{i.setButtonText("Sign in").setCta().onClick(async()=>{let s=this.plugin.settings.siteUrl.trim();if(!s){new h.Notice("Enter your site URL first.");return}i.setDisabled(!0),i.setButtonText("Opening browser\u2026");try{let r=await F.signIn(s);if(this.plugin.settings.accessToken=r.accessToken,this.plugin.settings.me=r.me,this.plugin.settings.authorizationEndpoint=r.authorizationEndpoint,this.plugin.settings.tokenEndpoint=r.tokenEndpoint,r.micropubEndpoint&&(this.plugin.settings.micropubEndpoint=r.micropubEndpoint),r.mediaEndpoint&&(this.plugin.settings.mediaEndpoint=r.mediaEndpoint),await this.plugin.saveSettings(),!this.plugin.settings.mediaEndpoint)try{let a=await new b(()=>this.plugin.settings.micropubEndpoint,()=>this.plugin.settings.mediaEndpoint,()=>this.plugin.settings.accessToken).fetchConfig();a["media-endpoint"]&&(this.plugin.settings.mediaEndpoint=a["media-endpoint"],await this.plugin.saveSettings())}catch(o){}new h.Notice(`\u2705 Signed in as ${r.me}`),this.display()}catch(r){new h.Notice(`Sign-in failed: ${String(r)}`,8e3),i.setDisabled(!1),i.setButtonText("Sign in")}})});let n=e.createEl("details");n.createEl("summary",{text:"Or paste a token manually",cls:"setting-item-description"}),n.style.marginTop="8px",n.style.marginBottom="8px",new h.Setting(n).setName("Access token").setDesc("Bearer token from your Indiekit admin panel.").addText(i=>{i.setPlaceholder("your-bearer-token").setValue(this.plugin.settings.accessToken).onChange(async s=>{this.plugin.settings.accessToken=s.trim(),await this.plugin.saveSettings()}),i.inputEl.type="password"}).addButton(i=>i.setButtonText("Verify").onClick(async()=>{if(!this.plugin.settings.micropubEndpoint||!this.plugin.settings.accessToken){new h.Notice("Set the Micropub endpoint and token first.");return}i.setDisabled(!0);try{await new b(()=>this.plugin.settings.micropubEndpoint,()=>this.plugin.settings.mediaEndpoint,()=>this.plugin.settings.accessToken).fetchConfig(),new h.Notice("\u2705 Token is valid!")}catch(s){new h.Notice(`Token check failed: ${String(s)}`)}finally{i.setDisabled(!1)}}))}renderSignedIn(e){let n=this.plugin.settings.me,i=e.createDiv({cls:"micropub-auth-banner"});i.style.cssText="display:flex;align-items:center;gap:12px;padding:12px 16px;border:1px solid var(--background-modifier-border);border-radius:8px;margin-bottom:16px;background:var(--background-secondary);";let s=i.createDiv();s.style.cssText="width:40px;height:40px;border-radius:50%;background:var(--interactive-accent);display:flex;align-items:center;justify-content:center;font-size:1.2rem;flex-shrink:0;",s.textContent="\u{1F310}";let r=i.createDiv();r.createEl("div",{text:"Signed in",attr:{style:"font-size:.75rem;color:var(--text-muted);margin-bottom:2px"}}),r.createEl("div",{text:n,attr:{style:"font-weight:500;word-break:break-all"}}),new h.Setting(e).setName("Site URL").addText(o=>o.setValue(this.plugin.settings.siteUrl).setDisabled(!0)).addButton(o=>o.setButtonText("Sign out").setWarning().onClick(async()=>{this.plugin.settings.accessToken="",this.plugin.settings.me="",this.plugin.settings.authorizationEndpoint="",this.plugin.settings.tokenEndpoint="",await this.plugin.saveSettings(),this.display()}))}};var Et=require("obsidian");var _="garden/",N=class{constructor(t,e){this.app=t;this.settings=e;this.client=new b(()=>e.micropubEndpoint,()=>e.mediaEndpoint,()=>e.accessToken)}async publish(t,e){let n=await this.app.vault.read(t),{frontmatter:i,body:s}=this.parseFrontmatter(n),r=i["mp-url"]!=null?String(i["mp-url"]):i.url!=null?String(i.url):void 0,{content:o,uploadedUrls:a}=await this.processImages(s),c=this.resolveWikilinks(o,t.path),l=this.buildProperties(i,c,a,t.basename,t.path,e),d;if(r){let p={};for(let[u,f]of Object.entries(l))p[u]=Array.isArray(f)?f:[f];d=await this.client.updatePost(r,p)}else d=await this.client.createPost(l);return d.success&&this.settings.writeUrlToFrontmatter&&(d.url?await this.writeUrlToNote(t,n,d.url,e):e!==void 0&&await this.writeSyndicateToNote(t,n,e)),d}buildProperties(t,e,n,i,s,r){var H,q,Y,J,Q,X,K,Z,tt,et,it,nt,st,rt,ot,at,ct,lt,dt,pt,gt,ut,ht,mt,ft,yt;let o={},a=e.trim(),c=(H=t.bookmarkOf)!=null?H:t["bookmark-of"],l=(q=t.likeOf)!=null?q:t["like-of"],d=(Y=t.inReplyTo)!=null?Y:t["in-reply-to"],p=(J=t.repostOf)!=null?J:t["repost-of"];c&&(o["bookmark-of"]=[String(c)]),l&&(o["like-of"]=[String(l)]),d&&(o["in-reply-to"]=[String(d)]),p&&(o["repost-of"]=[String(p)]),(l||p)&&!a||(o.content=a?[{html:a}]:[{html:""}]);let f=(K=(X=(Q=t.postType)!=null?Q:t.posttype)!=null?X:t["post-type"])!=null?K:t.type;if(f==="article"||!f&&!!((Z=t.title)!=null?Z:t.name)){let m=(et=(tt=t.title)!=null?tt:t.name)!=null?et:i;o.name=[String(m)]}((it=t.summary)!=null?it:t.excerpt)&&(o.summary=[String((nt=t.summary)!=null?nt:t.excerpt)]);let w=(st=t.created)!=null?st:t.date;w&&(o.published=[new Date(String(w)).toISOString()]);let E=[...this.resolveArray(t.tags),...this.resolveArray(t.category)],$=this.extractGardenStage(E),x=E.filter(m=>!m.startsWith(_)&&m!=="garden");if(x.length>0&&(o.category=[...new Set(x)]),this.settings.mapGardenTags){let m=(rt=t.gardenStage)!=null?rt:$;if(m&&(o.gardenStage=[m],m==="evergreen")){let S=t["evergreen-since"];S&&(o.evergreenSince=[String(S)])}}let D=r!==void 0?r:[...new Set([...this.settings.defaultSyndicateTo,...this.resolveArray((ot=t["mp-syndicate-to"])!=null?ot:t.mpSyndicateTo)])];D.length>0&&(o["mp-syndicate-to"]=D);let P=(at=t.visibility)!=null?at:this.settings.defaultVisibility;P&&P!=="public"&&(o.visibility=[P]);let v=t.ai&&typeof t.ai=="object"?t.ai:{},M=(lt=(ct=t["ai-text-level"])!=null?ct:t.aiTextLevel)!=null?lt:v.textLevel,G=(pt=(dt=t["ai-code-level"])!=null?dt:t.aiCodeLevel)!=null?pt:v.codeLevel,I=(ht=(ut=(gt=t["ai-tools"])!=null?gt:t.aiTools)!=null?ut:v.aiTools)!=null?ht:v.tools,z=(yt=(ft=(mt=t["ai-description"])!=null?mt:t.aiDescription)!=null?ft:v.aiDescription)!=null?yt:v.description;M!=null&&(o["ai-text-level"]=[String(M)]),G!=null&&(o["ai-code-level"]=[String(G)]),I!=null&&(o["ai-tools"]=[String(I)]),z!=null&&(o["ai-description"]=[String(z)]);let W=this.resolvePhotoArray(t.photo);W.length>0&&(o.photo=W);let V=this.resolveArray(t.related);if(V.length>0){let m=V.map(S=>this.resolveWikilinkToUrl(S,s)).filter(S=>S!==null);m.length>0&&(o.related=m)}for(let[m,S]of Object.entries(t))m.startsWith("mp-")&&m!=="mp-url"&&m!=="mp-syndicate-to"&&(o[m]=this.resolveArray(S));return o}resolvePhotoArray(t){return t?(Array.isArray(t)?t:[t]).map(n=>{var i,s;if(typeof n=="string")return{value:n};if(typeof n=="object"&&n!==null){let r=n,o=String((s=(i=r.url)!=null?i:r.value)!=null?s:"");return o?r.alt?{value:o,alt:String(r.alt)}:{value:o}:null}return null}).filter(n=>n!==null):[]}extractGardenStage(t){for(let e of t){let n=e.replace(/^#/,"");if(n.startsWith(_)){let i=n.slice(_.length);if(["plant","cultivate","evergreen","question","repot","revitalize","revisit"].includes(i))return i}}}async processImages(t){let e=[],n=/!\[\[([^\]]+\.(png|jpg|jpeg|gif|webp|svg))\]\]/gi,i=/!\[([^\]]*)\]\(([^)]+\.(png|jpg|jpeg|gif|webp|svg))\)/gi,s=t,r=[...t.matchAll(n)];for(let a of r){let c=a[1];try{let l=await this.uploadLocalFile(c);l&&(e.push(l),s=s.replace(a[0],`![${c}](${l})`))}catch(l){console.warn(`[micropub] Failed to upload ${c}:`,l)}}let o=[...s.matchAll(i)];for(let a of o){let c=a[1],l=a[2];if(!l.startsWith("http"))try{let d=await this.uploadLocalFile(l);d&&(e.push(d),s=s.replace(a[0],`![${c}](${d})`))}catch(d){console.warn(`[micropub] Failed to upload ${l}:`,d)}}return{content:s,uploadedUrls:e}}async uploadLocalFile(t){let e=this.app.vault.getFiles().find(s=>s.name===t||s.path===t);if(!e)return;let n=await this.app.vault.readBinary(e),i=this.guessMimeType(e.extension);return this.client.uploadMedia(n,e.name,i)}parseFrontmatter(t){var i;let e=t.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);if(!e)return{frontmatter:{},body:t};let n={};try{n=(i=(0,Et.parseYaml)(e[1]))!=null?i:{}}catch(s){}return{frontmatter:n,body:e[2]}}async writeUrlToNote(t,e,n,i){var d;let s=new Date,r=[s.getFullYear(),String(s.getMonth()+1).padStart(2,"0"),String(s.getDate()).padStart(2,"0")].join("-"),o=[["mp-url",`"${n}"`],["post-status","published"],["published",r]];if(i!==void 0&&o.push(["mp-syndicate-to",`[${i.join(", ")}]`]),this.settings.siteUrl)try{let p=new URL(this.settings.siteUrl).hostname.replace(/^www\./,"");o.push(["medium",`"[[${p}]]"`])}catch(p){}{let{frontmatter:p}=this.parseFrontmatter(e);if(!p["evergreen-since"]){let u=[...this.resolveArray(p.tags),...this.resolveArray(p.category)];((d=p.gardenStage)!=null?d:this.extractGardenStage(u))==="evergreen"&&o.push(["evergreen-since",r])}}let a=e.match(/^(---\r?\n[\s\S]*?\r?\n---\r?\n)([\s\S]*)$/);if(!a){let p=o.map(([u,f])=>`${u}: ${f}`).join(` +`,l=new TextEncoder().encode(r),c=new TextEncoder().encode(a),d=new Uint8Array(t),p=new Uint8Array(l.length+d.length+c.length);p.set(l,0),p.set(d,l.length),p.set(c,l.length+d.length);let u=await(0,T.requestUrl)({url:i,method:"POST",headers:{...this.authHeaders(),"Content-Type":`multipart/form-data; boundary=${s}`},body:p.buffer,throw:!1});if(u.status===201||u.status===202){let S=((h=u.headers)==null?void 0:h.location)||((b=u.headers)==null?void 0:b.Location)||((E=u.json)==null?void 0:E.url);if(S)return S}throw new Error(`Media upload failed (HTTP ${u.status}): ${this.extractError(u.text)}`)}authHeaders(){return{Authorization:`Bearer ${this.getToken()}`}}extractLinkRel(t,e){var s;let n=new RegExp(`]+rel=["']${e}["'][^>]+href=["']([^"']+)["']|]+href=["']([^"']+)["'][^>]+rel=["']${e}["']`,"i"),i=t.match(n);return(s=i==null?void 0:i[1])!=null?s:i==null?void 0:i[2]}async fetchConfigFrom(t){return(await(0,T.requestUrl)({url:`${t}?q=config`,method:"GET",headers:this.authHeaders()})).json}extractError(t){var e,n;try{let i=JSON.parse(t);return(n=(e=i.error_description)!=null?e:i.error)!=null?n:t.slice(0,200)}catch(i){return t.slice(0,200)}}};var M=Ft(require("crypto")),G=require("obsidian");var kt={cmdPublish:"Publish to Micropub",cmdUpdate:"Update existing Micropub post",noticeOpenNote:"Open a Markdown note to publish.",noticeNoEndpoint:"\u26A0\uFE0F Micropub endpoint not configured. Open plugin settings to add it.",noticeNoToken:"\u26A0\uFE0F Access token not configured. Open plugin settings to add it.",noticePublishing:"Publishing\u2026",noticePublished:"\u2705 Published!",noticePublishFailed:"\u274C Publish failed: {error}",noticeError:"\u274C Error: {error}",noticeNoSyndTargets:"\u26A0\uFE0F Could not fetch syndication targets. Publishing without dialog.",settingsTitle:"Micropub Publisher",settingsAccount:"Account",settingsEndpoints:"Endpoints",settingsEndpointsHint:"These are filled automatically when you sign in. Only edit them manually if your server uses non-standard paths.",settingsPublishBehaviour:"Publish Behaviour",settingsDigitalGarden:"Digital Garden",settingMicropubEndpoint:"Micropub endpoint",settingMicropubEndpointDesc:"e.g. https://example.com/micropub",settingMicropubEndpointPlaceholder:"https://example.com/micropub",settingMediaEndpoint:"Media endpoint",settingMediaEndpointDesc:"For image uploads. Auto-discovered if blank.",settingMediaEndpointPlaceholder:"https://example.com/micropub/media",settingVisibility:"Default visibility",settingVisibilityDesc:"Applies when the note has no explicit visibility property.",visibilityPublic:"Public",visibilityUnlisted:"Unlisted",visibilityPrivate:"Private",settingWriteUrl:"Write URL back to note",settingWriteUrlDesc:"After publishing, store the post URL as `mp-url` in frontmatter. Subsequent publishes will update the existing post instead of creating a new one.",settingSyndDialog:"Syndication dialog",settingSyndDialogDesc:"When to show the cross-posting dialog before publishing. 'When needed' shows it only if the note has no mp-syndicate-to frontmatter.",syndDialogWhenNeeded:"When needed",syndDialogAlways:"Always",syndDialogNever:"Never",settingSyndDefaults:"Default syndication targets",settingSyndDefaultsNone:"None configured. Targets checked by default in the publish dialog.",btnClearDefaults:"Clear defaults",settingGardenTags:"Map #garden/* tags to gardenStage",settingGardenTagsDesc:"Obsidian tags like #garden/plant become a `garden-stage: plant` Micropub property. The blog renders these as growth stage badges at /garden/.",settingGardenStages:"Stages: plant \u{1F331} \xB7 cultivate \u{1F33F} \xB7 question \u2753 \xB7 repot \u{1FAB4} \xB7 revitalize \u2728 \xB7 revisit \u{1F504}",settingSiteUrl:"Site URL",settingSiteUrlDesc:"Your site's home page. Clicking Sign in opens your blog's login page in the browser \u2014 the same flow iA Writer uses.",settingSiteUrlPlaceholder:"https://example.com",btnSignIn:"Sign in",btnOpeningBrowser:"Opening browser\u2026",noticeEnterSiteUrl:"Enter your site URL first.",noticeSignedInAs:"\u2705 Signed in as {me}",noticeSignInFailed:"Sign-in failed: {error}",lblSignedIn:"Signed in",btnSignOut:"Sign out",manualTokenSummary:"Or paste a token manually",settingAccessToken:"Access token",settingAccessTokenDesc:"Bearer token from your Indiekit admin panel.",settingAccessTokenPlaceholder:"your-bearer-token",btnVerify:"Verify",noticeSetEndpointFirst:"Set the Micropub endpoint and token first.",noticeTokenValid:"\u2705 Token is valid!",noticeTokenCheckFailed:"Token check failed: {error}",syndDialogTitle:"Syndication targets",syndDialogSubtitle:"Choose where to cross-post this note.",btnCancel:"Cancel",btnPublish:"Publish",errSignInTimeout:"Sign-in timed out (5 min). Please try again."};var Tt={cmdPublish:"An Micropub ver\xF6ffentlichen",cmdUpdate:"Bestehenden Micropub-Beitrag aktualisieren",noticeOpenNote:"\xD6ffne eine Markdown-Notiz zum Ver\xF6ffentlichen.",noticeNoEndpoint:"\u26A0\uFE0F Micropub-Endpunkt nicht konfiguriert. Bitte in den Plugin-Einstellungen eintragen.",noticeNoToken:"\u26A0\uFE0F Zugriffstoken nicht konfiguriert. Bitte in den Plugin-Einstellungen eintragen.",noticePublishing:"Wird ver\xF6ffentlicht\u2026",noticePublished:"\u2705 Ver\xF6ffentlicht!",noticePublishFailed:"\u274C Ver\xF6ffentlichung fehlgeschlagen: {error}",noticeError:"\u274C Fehler: {error}",noticeNoSyndTargets:"\u26A0\uFE0F Syndizierungsziele konnten nicht abgerufen werden. Ver\xF6ffentlichung ohne Dialog.",settingsTitle:"Micropub Publisher",settingsAccount:"Konto",settingsEndpoints:"Endpunkte",settingsEndpointsHint:"Diese werden beim Anmelden automatisch ausgef\xFCllt. Nur manuell bearbeiten, wenn der Server nicht standardm\xE4\xDFige Pfade verwendet.",settingsPublishBehaviour:"Ver\xF6ffentlichungsverhalten",settingsDigitalGarden:"Digitaler Garten",settingMicropubEndpoint:"Micropub-Endpunkt",settingMicropubEndpointDesc:"z. B. https://example.com/micropub",settingMicropubEndpointPlaceholder:"https://example.com/micropub",settingMediaEndpoint:"Medien-Endpunkt",settingMediaEndpointDesc:"F\xFCr Bild-Uploads. Wird automatisch ermittelt, wenn leer.",settingMediaEndpointPlaceholder:"https://example.com/micropub/media",settingVisibility:"Standard-Sichtbarkeit",settingVisibilityDesc:"Gilt, wenn die Notiz keine explizite Sichtbarkeits-Eigenschaft hat.",visibilityPublic:"\xD6ffentlich",visibilityUnlisted:"Nicht gelistet",visibilityPrivate:"Privat",settingWriteUrl:"URL zur\xFCck in Notiz schreiben",settingWriteUrlDesc:"Nach der Ver\xF6ffentlichung wird die Beitrags-URL als `mp-url` im Frontmatter gespeichert. Sp\xE4tere Ver\xF6ffentlichungen aktualisieren den Beitrag statt einen neuen zu erstellen.",settingSyndDialog:"Syndizierungsdialog",settingSyndDialogDesc:"Wann der Dialog zum Querverweis vor der Ver\xF6ffentlichung angezeigt wird. 'Bei Bedarf' zeigt ihn nur, wenn kein mp-syndicate-to im Frontmatter vorhanden ist.",syndDialogWhenNeeded:"Bei Bedarf",syndDialogAlways:"Immer",syndDialogNever:"Nie",settingSyndDefaults:"Standard-Syndizierungsziele",settingSyndDefaultsNone:"Keine konfiguriert. Im Ver\xF6ffentlichungsdialog standardm\xE4\xDFig aktivierte Ziele.",btnClearDefaults:"Standards l\xF6schen",settingGardenTags:"#garden/*-Tags zu gardenStage zuordnen",settingGardenTagsDesc:"Obsidian-Tags wie #garden/plant werden zur Micropub-Eigenschaft `garden-stage: plant`. Der Blog zeigt diese als Wachstumsstufen-Abzeichen unter /garden/ an.",settingGardenStages:"Stufen: plant \u{1F331} \xB7 cultivate \u{1F33F} \xB7 question \u2753 \xB7 repot \u{1FAB4} \xB7 revitalize \u2728 \xB7 revisit \u{1F504}",settingSiteUrl:"Website-URL",settingSiteUrlDesc:"Startseite deiner Website. Klick auf Anmelden \xF6ffnet die Login-Seite deines Blogs im Browser.",settingSiteUrlPlaceholder:"https://example.com",btnSignIn:"Anmelden",btnOpeningBrowser:"Browser wird ge\xF6ffnet\u2026",noticeEnterSiteUrl:"Bitte zuerst die Website-URL eingeben.",noticeSignedInAs:"\u2705 Angemeldet als {me}",noticeSignInFailed:"Anmeldung fehlgeschlagen: {error}",lblSignedIn:"Angemeldet",btnSignOut:"Abmelden",manualTokenSummary:"Oder Token manuell einf\xFCgen",settingAccessToken:"Zugriffstoken",settingAccessTokenDesc:"Bearer-Token aus deinem Indiekit-Adminbereich.",settingAccessTokenPlaceholder:"your-bearer-token",btnVerify:"Pr\xFCfen",noticeSetEndpointFirst:"Bitte zuerst Micropub-Endpunkt und Token eingeben.",noticeTokenValid:"\u2705 Token ist g\xFCltig!",noticeTokenCheckFailed:"Token-Pr\xFCfung fehlgeschlagen: {error}",syndDialogTitle:"Syndizierungsziele",syndDialogSubtitle:"Wo soll diese Notiz gleichzeitig ver\xF6ffentlicht werden?",btnCancel:"Abbrechen",btnPublish:"Ver\xF6ffentlichen",errSignInTimeout:"Anmeldung abgelaufen (5 Min.). Bitte erneut versuchen."};var L={en:kt,de:Tt};function o(g,t){var s,r,a,l,c;let e=((r=(s=window.moment)==null?void 0:s.locale())!=null?r:"en").split("-")[0],i=(c=(l=((a=L[e])!=null?a:L.en)[g])!=null?l:L.en[g])!=null?c:g;if(t)for(let[d,p]of Object.entries(t))i=i.split(`{${d}}`).join(p);return i}var Et="https://svemagie.github.io/obsidian-micropub/",Pt="https://svemagie.github.io/obsidian-micropub/callback",xt="create update media",zt=300*1e3,A=null;function Dt(g){if(!A)return;let{resolve:t,state:e}=A;A=null,t(g)}var F=class g{static async discoverEndpoints(t){let n=(await(0,G.requestUrl)({url:t,method:"GET"})).text,i=g.extractLinkRel(n,"authorization_endpoint"),s=g.extractLinkRel(n,"token_endpoint"),r=g.extractLinkRel(n,"micropub");if(!i)throw new Error(`No found at ${t}. Make sure Indiekit is running and SITE_URL is set correctly.`);if(!s)throw new Error(`No found at ${t}.`);return{authorizationEndpoint:i,tokenEndpoint:s,micropubEndpoint:r}}static async signIn(t){var b,E,S,P,R,x;let{authorizationEndpoint:e,tokenEndpoint:n,micropubEndpoint:i}=await g.discoverEndpoints(t),s=g.base64url(M.randomBytes(16)),r=g.base64url(M.randomBytes(64)),a=g.base64url(M.createHash("sha256").update(r).digest()),l=new Promise((C,D)=>{let v=setTimeout(()=>{A=null,D(new Error(o("errSignInTimeout")))},zt);A={state:s,resolve:N=>{clearTimeout(v),C(N)}}}),c=new URL(e);c.searchParams.set("response_type","code"),c.searchParams.set("client_id",Et),c.searchParams.set("redirect_uri",Pt),c.searchParams.set("state",s),c.searchParams.set("code_challenge",a),c.searchParams.set("code_challenge_method","S256"),c.searchParams.set("scope",xt),c.searchParams.set("me",t),window.open(c.toString());let d=await l;if(d.state!==s)throw new Error("State mismatch \u2014 possible CSRF attack. Please try again.");let p=d.code;if(!p)throw new Error((E=(b=d.error_description)!=null?b:d.error)!=null?E:"No authorization code received.");let u=await(0,G.requestUrl)({url:n,method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},body:new URLSearchParams({grant_type:"authorization_code",code:p,client_id:Et,redirect_uri:Pt,code_verifier:r}).toString(),throw:!1}),h=u.json;if(!h.access_token)throw new Error((P=(S=h.error_description)!=null?S:h.error)!=null?P:`Token exchange failed (HTTP ${u.status})`);return{accessToken:h.access_token,scope:(R=h.scope)!=null?R:xt,me:(x=h.me)!=null?x:t,authorizationEndpoint:e,tokenEndpoint:n,micropubEndpoint:i}}static base64url(t){return t.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}static extractLinkRel(t,e){var s;let n=new RegExp(`]+rel=["'][^"']*\\b${e}\\b[^"']*["'][^>]+href=["']([^"']+)["']|]+href=["']([^"']+)["'][^>]+rel=["'][^"']*\\b${e}\\b[^"']*["']`,"i"),i=t.match(n);return(s=i==null?void 0:i[1])!=null?s:i==null?void 0:i[2]}};var $=class extends m.PluginSettingTab{constructor(e,n){super(e,n);this.plugin=n}display(){let{containerEl:e}=this;e.empty(),e.createEl("h2",{text:o("settingsTitle")}),e.createEl("h3",{text:o("settingsAccount")}),this.plugin.settings.me&&this.plugin.settings.accessToken?this.renderSignedIn(e):this.renderSignedOut(e),e.createEl("h3",{text:o("settingsEndpoints")}),e.createEl("p",{text:o("settingsEndpointsHint"),cls:"setting-item-description"}),new m.Setting(e).setName(o("settingMicropubEndpoint")).setDesc(o("settingMicropubEndpointDesc")).addText(s=>s.setPlaceholder(o("settingMicropubEndpointPlaceholder")).setValue(this.plugin.settings.micropubEndpoint).onChange(async r=>{this.plugin.settings.micropubEndpoint=r.trim(),await this.plugin.saveSettings()})),new m.Setting(e).setName(o("settingMediaEndpoint")).setDesc(o("settingMediaEndpointDesc")).addText(s=>s.setPlaceholder(o("settingMediaEndpointPlaceholder")).setValue(this.plugin.settings.mediaEndpoint).onChange(async r=>{this.plugin.settings.mediaEndpoint=r.trim(),await this.plugin.saveSettings()})),e.createEl("h3",{text:o("settingsPublishBehaviour")}),new m.Setting(e).setName(o("settingVisibility")).setDesc(o("settingVisibilityDesc")).addDropdown(s=>s.addOption("public",o("visibilityPublic")).addOption("unlisted",o("visibilityUnlisted")).addOption("private",o("visibilityPrivate")).setValue(this.plugin.settings.defaultVisibility).onChange(async r=>{this.plugin.settings.defaultVisibility=r,await this.plugin.saveSettings()})),new m.Setting(e).setName(o("settingWriteUrl")).setDesc(o("settingWriteUrlDesc")).addToggle(s=>s.setValue(this.plugin.settings.writeUrlToFrontmatter).onChange(async r=>{this.plugin.settings.writeUrlToFrontmatter=r,await this.plugin.saveSettings()})),new m.Setting(e).setName(o("settingSyndDialog")).setDesc(o("settingSyndDialogDesc")).addDropdown(s=>s.addOption("when-needed",o("syndDialogWhenNeeded")).addOption("always",o("syndDialogAlways")).addOption("never",o("syndDialogNever")).setValue(this.plugin.settings.showSyndicationDialog).onChange(async r=>{this.plugin.settings.showSyndicationDialog=r,await this.plugin.saveSettings()}));let n=this.plugin.settings.defaultSyndicateTo,i=new m.Setting(e).setName(o("settingSyndDefaults")).setDesc(n.length>0?n.join(", "):o("settingSyndDefaultsNone"));n.length>0&&i.addButton(s=>s.setButtonText(o("btnClearDefaults")).setWarning().onClick(async()=>{this.plugin.settings.defaultSyndicateTo=[],await this.plugin.saveSettings(),this.display()})),e.createEl("h3",{text:o("settingsDigitalGarden")}),new m.Setting(e).setName(o("settingGardenTags")).setDesc(o("settingGardenTagsDesc")).addToggle(s=>s.setValue(this.plugin.settings.mapGardenTags).onChange(async r=>{this.plugin.settings.mapGardenTags=r,await this.plugin.saveSettings()})),e.createEl("p",{text:o("settingGardenStages"),cls:"setting-item-description"})}renderSignedOut(e){new m.Setting(e).setName(o("settingSiteUrl")).setDesc(o("settingSiteUrlDesc")).addText(i=>i.setPlaceholder(o("settingSiteUrlPlaceholder")).setValue(this.plugin.settings.siteUrl).onChange(async s=>{this.plugin.settings.siteUrl=s.trim(),await this.plugin.saveSettings()})).addButton(i=>{i.setButtonText(o("btnSignIn")).setCta().onClick(async()=>{let s=this.plugin.settings.siteUrl.trim();if(!s){new m.Notice(o("noticeEnterSiteUrl"));return}i.setDisabled(!0),i.setButtonText(o("btnOpeningBrowser"));try{let r=await F.signIn(s);if(this.plugin.settings.accessToken=r.accessToken,this.plugin.settings.me=r.me,this.plugin.settings.authorizationEndpoint=r.authorizationEndpoint,this.plugin.settings.tokenEndpoint=r.tokenEndpoint,r.micropubEndpoint&&(this.plugin.settings.micropubEndpoint=r.micropubEndpoint),r.mediaEndpoint&&(this.plugin.settings.mediaEndpoint=r.mediaEndpoint),await this.plugin.saveSettings(),!this.plugin.settings.mediaEndpoint)try{let l=await new w(()=>this.plugin.settings.micropubEndpoint,()=>this.plugin.settings.mediaEndpoint,()=>this.plugin.settings.accessToken).fetchConfig();l["media-endpoint"]&&(this.plugin.settings.mediaEndpoint=l["media-endpoint"],await this.plugin.saveSettings())}catch(a){}new m.Notice(o("noticeSignedInAs",{me:r.me})),this.display()}catch(r){new m.Notice(o("noticeSignInFailed",{error:String(r)}),8e3),i.setDisabled(!1),i.setButtonText(o("btnSignIn"))}})});let n=e.createEl("details");n.createEl("summary",{text:o("manualTokenSummary"),cls:"setting-item-description"}),n.style.marginTop="8px",n.style.marginBottom="8px",new m.Setting(n).setName(o("settingAccessToken")).setDesc(o("settingAccessTokenDesc")).addText(i=>{i.setPlaceholder(o("settingAccessTokenPlaceholder")).setValue(this.plugin.settings.accessToken).onChange(async s=>{this.plugin.settings.accessToken=s.trim(),await this.plugin.saveSettings()}),i.inputEl.type="password"}).addButton(i=>i.setButtonText(o("btnVerify")).onClick(async()=>{if(!this.plugin.settings.micropubEndpoint||!this.plugin.settings.accessToken){new m.Notice(o("noticeSetEndpointFirst"));return}i.setDisabled(!0);try{await new w(()=>this.plugin.settings.micropubEndpoint,()=>this.plugin.settings.mediaEndpoint,()=>this.plugin.settings.accessToken).fetchConfig(),new m.Notice(o("noticeTokenValid"))}catch(s){new m.Notice(o("noticeTokenCheckFailed",{error:String(s)}))}finally{i.setDisabled(!1)}}))}renderSignedIn(e){let n=this.plugin.settings.me,i=e.createDiv({cls:"micropub-auth-banner"});i.style.cssText="display:flex;align-items:center;gap:12px;padding:12px 16px;border:1px solid var(--background-modifier-border);border-radius:8px;margin-bottom:16px;background:var(--background-secondary);";let s=i.createDiv();s.style.cssText="width:40px;height:40px;border-radius:50%;background:var(--interactive-accent);display:flex;align-items:center;justify-content:center;font-size:1.2rem;flex-shrink:0;",s.textContent="\u{1F310}";let r=i.createDiv();r.createEl("div",{text:o("lblSignedIn"),attr:{style:"font-size:.75rem;color:var(--text-muted);margin-bottom:2px"}}),r.createEl("div",{text:n,attr:{style:"font-weight:500;word-break:break-all"}}),new m.Setting(e).setName(o("settingSiteUrl")).addText(a=>a.setValue(this.plugin.settings.siteUrl).setDisabled(!0)).addButton(a=>a.setButtonText(o("btnSignOut")).setWarning().onClick(async()=>{this.plugin.settings.accessToken="",this.plugin.settings.me="",this.plugin.settings.authorizationEndpoint="",this.plugin.settings.tokenEndpoint="",await this.plugin.saveSettings(),this.display()}))}};var At=require("obsidian");var V="garden/",z=class{constructor(t,e){this.app=t;this.settings=e;this.client=new w(()=>e.micropubEndpoint,()=>e.mediaEndpoint,()=>e.accessToken)}async publish(t,e){let n=await this.app.vault.read(t),{frontmatter:i,body:s}=this.parseFrontmatter(n),r=i["mp-url"]!=null?String(i["mp-url"]):i.url!=null?String(i.url):void 0,{content:a,uploadedUrls:l}=await this.processImages(s),c=this.resolveWikilinks(a,t.path),d=this.buildProperties(i,c,l,t.basename,t.path,e),p;if(r){let u={};for(let[h,b]of Object.entries(d))u[h]=Array.isArray(b)?b:[b];p=await this.client.updatePost(r,u)}else p=await this.client.createPost(d);return p.success&&this.settings.writeUrlToFrontmatter&&(p.url?await this.writeUrlToNote(t,n,p.url,e):e!==void 0&&await this.writeSyndicateToNote(t,n,e)),p}buildProperties(t,e,n,i,s,r){var Y,J,K,Z,Q,X,tt,et,it,nt,st,rt,ot,at,lt,ct,dt,gt,pt,ut,ht,mt,ft,bt,yt,wt;let a={},l=e.trim(),c=(Y=t.bookmarkOf)!=null?Y:t["bookmark-of"],d=(J=t.likeOf)!=null?J:t["like-of"],p=(K=t.inReplyTo)!=null?K:t["in-reply-to"],u=(Z=t.repostOf)!=null?Z:t["repost-of"];c&&(a["bookmark-of"]=[String(c)]),d&&(a["like-of"]=[String(d)]),p&&(a["in-reply-to"]=[String(p)]),u&&(a["repost-of"]=[String(u)]),(d||u)&&!l||(a.content=l?[{html:l}]:[{html:""}]);let b=(tt=(X=(Q=t.postType)!=null?Q:t.posttype)!=null?X:t["post-type"])!=null?tt:t.type;if(b==="article"||!b&&!!((et=t.title)!=null?et:t.name)){let f=(nt=(it=t.title)!=null?it:t.name)!=null?nt:i;a.name=[String(f)]}((st=t.summary)!=null?st:t.excerpt)&&(a.summary=[String((rt=t.summary)!=null?rt:t.excerpt)]);let S=(ot=t.created)!=null?ot:t.date;S&&(a.published=[new Date(String(S)).toISOString()]);let P=[...this.resolveArray(t.tags),...this.resolveArray(t.category)],R=this.extractGardenStage(P),x=P.filter(f=>!f.startsWith(V)&&f!=="garden");if(x.length>0&&(a.category=[...new Set(x)]),this.settings.mapGardenTags){let f=(at=t.gardenStage)!=null?at:R;if(f&&(a.gardenStage=[f],f==="evergreen")){let k=t["evergreen-since"];k&&(a.evergreenSince=[String(k)])}}let C=r!==void 0?r:[...new Set([...this.settings.defaultSyndicateTo,...this.resolveArray((lt=t["mp-syndicate-to"])!=null?lt:t.mpSyndicateTo)])];C.length>0&&(a["mp-syndicate-to"]=C);let D=(ct=t.visibility)!=null?ct:this.settings.defaultVisibility;D&&D!=="public"&&(a.visibility=[D]);let v=t.ai&&typeof t.ai=="object"?t.ai:{},N=(gt=(dt=t["ai-text-level"])!=null?dt:t.aiTextLevel)!=null?gt:v.textLevel,W=(ut=(pt=t["ai-code-level"])!=null?pt:t.aiCodeLevel)!=null?ut:v.codeLevel,j=(ft=(mt=(ht=t["ai-tools"])!=null?ht:t.aiTools)!=null?mt:v.aiTools)!=null?ft:v.tools,_=(wt=(yt=(bt=t["ai-description"])!=null?bt:t.aiDescription)!=null?yt:v.aiDescription)!=null?wt:v.description;N!=null&&(a["ai-text-level"]=[String(N)]),W!=null&&(a["ai-code-level"]=[String(W)]),j!=null&&(a["ai-tools"]=[String(j)]),_!=null&&(a["ai-description"]=[String(_)]);let H=this.resolvePhotoArray(t.photo);H.length>0&&(a.photo=H);let q=this.resolveArray(t.related);if(q.length>0){let f=q.map(k=>this.resolveWikilinkToUrl(k,s)).filter(k=>k!==null);f.length>0&&(a.related=f)}for(let[f,k]of Object.entries(t))f.startsWith("mp-")&&f!=="mp-url"&&f!=="mp-syndicate-to"&&(a[f]=this.resolveArray(k));return a}resolvePhotoArray(t){return t?(Array.isArray(t)?t:[t]).map(n=>{var i,s;if(typeof n=="string")return{value:n};if(typeof n=="object"&&n!==null){let r=n,a=String((s=(i=r.url)!=null?i:r.value)!=null?s:"");return a?r.alt?{value:a,alt:String(r.alt)}:{value:a}:null}return null}).filter(n=>n!==null):[]}extractGardenStage(t){for(let e of t){let n=e.replace(/^#/,"");if(n.startsWith(V)){let i=n.slice(V.length);if(["plant","cultivate","evergreen","question","repot","revitalize","revisit"].includes(i))return i}}}async processImages(t){let e=[],n=/!\[\[([^\]]+\.(png|jpg|jpeg|gif|webp|svg))\]\]/gi,i=/!\[([^\]]*)\]\(([^)]+\.(png|jpg|jpeg|gif|webp|svg))\)/gi,s=t,r=[...t.matchAll(n)];for(let l of r){let c=l[1];try{let d=await this.uploadLocalFile(c);d&&(e.push(d),s=s.replace(l[0],`![${c}](${d})`))}catch(d){console.warn(`[micropub] Failed to upload ${c}:`,d)}}let a=[...s.matchAll(i)];for(let l of a){let c=l[1],d=l[2];if(!d.startsWith("http"))try{let p=await this.uploadLocalFile(d);p&&(e.push(p),s=s.replace(l[0],`![${c}](${p})`))}catch(p){console.warn(`[micropub] Failed to upload ${d}:`,p)}}return{content:s,uploadedUrls:e}}async uploadLocalFile(t){let e=this.app.vault.getFiles().find(s=>s.name===t||s.path===t);if(!e)return;let n=await this.app.vault.readBinary(e),i=this.guessMimeType(e.extension);return this.client.uploadMedia(n,e.name,i)}parseFrontmatter(t){var i;let e=t.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);if(!e)return{frontmatter:{},body:t};let n={};try{n=(i=(0,At.parseYaml)(e[1]))!=null?i:{}}catch(s){}return{frontmatter:n,body:e[2]}}async writeUrlToNote(t,e,n,i){var p;let s=new Date,r=[s.getFullYear(),String(s.getMonth()+1).padStart(2,"0"),String(s.getDate()).padStart(2,"0")].join("-"),a=[["mp-url",`"${n}"`],["post-status","published"],["published",r]];if(i!==void 0&&a.push(["mp-syndicate-to",`[${i.join(", ")}]`]),this.settings.siteUrl)try{let u=new URL(this.settings.siteUrl).hostname.replace(/^www\./,"");a.push(["medium",`"[[${u}]]"`])}catch(u){}{let{frontmatter:u}=this.parseFrontmatter(e);if(!u["evergreen-since"]){let h=[...this.resolveArray(u.tags),...this.resolveArray(u.category)];((p=u.gardenStage)!=null?p:this.extractGardenStage(h))==="evergreen"&&a.push(["evergreen-since",r])}}let l=e.match(/^(---\r?\n[\s\S]*?\r?\n---\r?\n)([\s\S]*)$/);if(!l){let u=a.map(([h,b])=>`${h}: ${b}`).join(` `);await this.app.vault.modify(t,`--- -${p} +${u} --- -`+e);return}let c=a[1],l=a[2];for(let[p,u]of o)c=this.setFrontmatterField(c,p,u);await this.app.vault.modify(t,c+l)}async writeSyndicateToNote(t,e,n){let i=e.match(/^(---\r?\n[\s\S]*?\r?\n---\r?\n)([\s\S]*)$/),s=`[${n.join(", ")}]`;if(!i){await this.app.vault.modify(t,`--- +`+e);return}let c=l[1],d=l[2];for(let[u,h]of a)c=this.setFrontmatterField(c,u,h);await this.app.vault.modify(t,c+d)}async writeSyndicateToNote(t,e,n){let i=e.match(/^(---\r?\n[\s\S]*?\r?\n---\r?\n)([\s\S]*)$/),s=`[${n.join(", ")}]`;if(!i){await this.app.vault.modify(t,`--- mp-syndicate-to: ${s} --- `+e);return}let r=this.setFrontmatterField(i[1],"mp-syndicate-to",s);await this.app.vault.modify(t,r+i[2])}setFrontmatterField(t,e,n){let i=new RegExp(`^${e}:.*$`,"m");return i.test(t)?t.replace(i,`${e}: ${n}`):t.replace(/(\r?\n---\r?\n)$/,` -${e}: ${n}$1`)}resolveWikilinks(t,e){return t.replace(/(?{let o=i.trim(),a=(r==null?void 0:r.trim())||o.split("/").pop()||o,c=this.resolveWikilinkToUrl(`[[${o}]]`,e);if(!c)return a;let l=s?s.toLowerCase().replace(/\s+/g,"-"):"";return`[${a}](${c}${l})`})}resolveWikilinkToUrl(t,e){var s,r,o;if(t.startsWith("http"))return t;let n=t.match(/^\[\[([^\]|#]+)(?:#[^\]|]*)?\|?[^\]]*\]\]$/);if(!n)return null;let i=this.app.metadataCache.getFirstLinkpathDest(n[1].trim(),e);return i&&(o=(r=(s=this.app.metadataCache.getFileCache(i))==null?void 0:s.frontmatter)==null?void 0:r["mp-url"])!=null?o:null}resolveArray(t){return t?Array.isArray(t)?t.map(String):[String(t)]:[]}guessMimeType(t){var n;return(n={png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml"}[t.toLowerCase()])!=null?n:"application/octet-stream"}};var C=require("obsidian"),B=class extends C.Modal{constructor(e,n,i){super(e);this.targets=n;this.resolvePromise=null;this.resolved=!1;this.selected=new Set(i.filter(s=>n.some(r=>r.uid===s)))}async awaitSelection(){return new Promise(e=>{this.resolvePromise=e,this.open()})}onOpen(){let{contentEl:e}=this;e.createEl("h2",{text:"Syndication targets"}),e.createEl("p",{text:"Choose where to cross-post this note.",cls:"setting-item-description"});for(let n of this.targets)new C.Setting(e).setName(n.name).addToggle(i=>i.setValue(this.selected.has(n.uid)).onChange(s=>{s?this.selected.add(n.uid):this.selected.delete(n.uid)}));new C.Setting(e).addButton(n=>n.setButtonText("Cancel").onClick(()=>{this.finish(null)})).addButton(n=>n.setButtonText("Publish").setCta().onClick(()=>{this.finish([...this.selected])}))}onClose(){this.finish(null),this.contentEl.empty()}finish(e){var n;this.resolved||(this.resolved=!0,(n=this.resolvePromise)==null||n.call(this,e),this.resolvePromise=null)}};var O=class extends y.Plugin{async onload(){await this.loadSettings(),this.addCommand({id:"publish-to-micropub",name:"Publish to Micropub",checkCallback:t=>{let e=this.app.workspace.getActiveFile();return!e||e.extension!=="md"?!1:(t||this.publishActiveNote(e),!0)}}),this.addCommand({id:"publish-to-micropub-update",name:"Update existing Micropub post",checkCallback:t=>{let e=this.app.workspace.getActiveFile();return!e||e.extension!=="md"?!1:(t||this.publishActiveNote(e),!0)}}),this.registerObsidianProtocolHandler("micropub-auth",t=>{Tt(t)}),this.addSettingTab(new L(this.app,this)),this.addRibbonIcon("send","Publish to Micropub",()=>{let t=this.app.workspace.getActiveFile();if(!t||t.extension!=="md"){new y.Notice("Open a Markdown note to publish.");return}this.publishActiveNote(t)})}onunload(){}async publishActiveNote(t){if(!this.settings.micropubEndpoint){new y.Notice("\u26A0\uFE0F Micropub endpoint not configured. Open plugin settings to add it.");return}if(!this.settings.accessToken){new y.Notice("\u26A0\uFE0F Access token not configured. Open plugin settings to add it.");return}let e=await this.resolveSyndicationTargets(t);if(e===null)return;let n=new y.Notice("Publishing\u2026",0);try{let s=await new N(this.app,this.settings).publish(t,e);if(n.hide(),s.success){let r=s.url?` -${s.url}`:"";new y.Notice(`\u2705 Published!${r}`,8e3)}else new y.Notice(`\u274C Publish failed: ${s.error}`,1e4),console.error("[micropub] Publish failed:",s.error)}catch(i){n.hide();let s=i instanceof Error?i.message:String(i);new y.Notice(`\u274C Error: ${s}`,1e4),console.error("[micropub] Unexpected error:",i)}}async resolveSyndicationTargets(t){var a,c;let e=this.settings.showSyndicationDialog;if(e==="never")return;let n=[];try{n=(a=(await new b(()=>this.settings.micropubEndpoint,()=>this.settings.mediaEndpoint,()=>this.settings.accessToken).fetchConfig())["syndicate-to"])!=null?a:[]}catch(l){new y.Notice("\u26A0\uFE0F Could not fetch syndication targets. Publishing without dialog.",4e3);return}if(n.length===0)return;let i;try{let d=(await this.app.vault.read(t)).match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);if(d){let u=((c=(0,y.parseYaml)(d[1]))!=null?c:{})["mp-syndicate-to"];u!==void 0&&(i=Array.isArray(u)?u.map(String):[String(u)])}}catch(l){}if(!(e==="always"||e==="when-needed"&&i===void 0||i!==void 0&&i.length===0))return;let r=i&&i.length>0?i:this.settings.defaultSyndicateTo;return new B(this.app,n,r).awaitSelection()}async loadSettings(){this.settings=Object.assign({},wt,await this.loadData())}async saveSettings(){await this.saveData(this.settings)}}; +${e}: ${n}$1`)}resolveWikilinks(t,e){return t.replace(/(?{let a=i.trim(),l=(r==null?void 0:r.trim())||a.split("/").pop()||a,c=this.resolveWikilinkToUrl(`[[${a}]]`,e);if(!c)return l;let d=s?s.toLowerCase().replace(/\s+/g,"-"):"";return`[${l}](${c}${d})`})}resolveWikilinkToUrl(t,e){var s,r,a;if(t.startsWith("http"))return t;let n=t.match(/^\[\[([^\]|#]+)(?:#[^\]|]*)?\|?[^\]]*\]\]$/);if(!n)return null;let i=this.app.metadataCache.getFirstLinkpathDest(n[1].trim(),e);return i&&(a=(r=(s=this.app.metadataCache.getFileCache(i))==null?void 0:s.frontmatter)==null?void 0:r["mp-url"])!=null?a:null}resolveArray(t){return t?Array.isArray(t)?t.map(String):[String(t)]:[]}guessMimeType(t){var n;return(n={png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml"}[t.toLowerCase()])!=null?n:"application/octet-stream"}};var U=require("obsidian");var O=class extends U.Modal{constructor(e,n,i){super(e);this.targets=n;this.resolvePromise=null;this.resolved=!1;this.selected=new Set(i.filter(s=>n.some(r=>r.uid===s)))}async awaitSelection(){return new Promise(e=>{this.resolvePromise=e,this.open()})}onOpen(){let{contentEl:e}=this;e.createEl("h2",{text:o("syndDialogTitle")}),e.createEl("p",{text:o("syndDialogSubtitle"),cls:"setting-item-description"});for(let n of this.targets)new U.Setting(e).setName(n.name).addToggle(i=>i.setValue(this.selected.has(n.uid)).onChange(s=>{s?this.selected.add(n.uid):this.selected.delete(n.uid)}));new U.Setting(e).addButton(n=>n.setButtonText(o("btnCancel")).onClick(()=>{this.finish(null)})).addButton(n=>n.setButtonText(o("btnPublish")).setCta().onClick(()=>{this.finish([...this.selected])}))}onClose(){this.finish(null),this.contentEl.empty()}finish(e){var n;this.resolved||(this.resolved=!0,(n=this.resolvePromise)==null||n.call(this,e),this.resolvePromise=null)}};var I=class extends y.Plugin{async onload(){await this.loadSettings(),this.addCommand({id:"publish-to-micropub",name:o("cmdPublish"),checkCallback:t=>{let e=this.app.workspace.getActiveFile();return!e||e.extension!=="md"?!1:(t||this.publishActiveNote(e),!0)}}),this.addCommand({id:"publish-to-micropub-update",name:o("cmdUpdate"),checkCallback:t=>{let e=this.app.workspace.getActiveFile();return!e||e.extension!=="md"?!1:(t||this.publishActiveNote(e),!0)}}),this.registerObsidianProtocolHandler("micropub-auth",t=>{Dt(t)}),this.addSettingTab(new $(this.app,this)),this.addRibbonIcon("send",o("cmdPublish"),()=>{let t=this.app.workspace.getActiveFile();if(!t||t.extension!=="md"){new y.Notice(o("noticeOpenNote"));return}this.publishActiveNote(t)})}onunload(){}async publishActiveNote(t){var i;if(!this.settings.micropubEndpoint){new y.Notice(o("noticeNoEndpoint"));return}if(!this.settings.accessToken){new y.Notice(o("noticeNoToken"));return}let e=await this.resolveSyndicationTargets(t);if(e===null)return;let n=new y.Notice(o("noticePublishing"),0);try{let r=await new z(this.app,this.settings).publish(t,e);if(n.hide(),r.success){let a=r.url?` +${r.url}`:"";new y.Notice(`${o("noticePublished")}${a}`,8e3)}else new y.Notice(o("noticePublishFailed",{error:(i=r.error)!=null?i:""}),1e4),console.error("[micropub] Publish failed:",r.error)}catch(s){n.hide();let r=s instanceof Error?s.message:String(s);new y.Notice(o("noticeError",{error:r}),1e4),console.error("[micropub] Unexpected error:",s)}}async resolveSyndicationTargets(t){var l,c;let e=this.settings.showSyndicationDialog;if(e==="never")return;let n=[];try{n=(l=(await new w(()=>this.settings.micropubEndpoint,()=>this.settings.mediaEndpoint,()=>this.settings.accessToken).fetchConfig())["syndicate-to"])!=null?l:[]}catch(d){new y.Notice(o("noticeNoSyndTargets"),4e3);return}if(n.length===0)return;let i;try{let p=(await this.app.vault.read(t)).match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);if(p){let h=((c=(0,y.parseYaml)(p[1]))!=null?c:{})["mp-syndicate-to"];h!==void 0&&(i=Array.isArray(h)?h.map(String):[String(h)])}}catch(d){}if(!(e==="always"||e==="when-needed"&&i===void 0||i!==void 0&&i.length===0))return;let r=i&&i.length>0?i:this.settings.defaultSyndicateTo;return new O(this.app,n,r).awaitSelection()}async loadSettings(){this.settings=Object.assign({},vt,await this.loadData())}async saveSettings(){await this.saveData(this.settings)}};