From d8509e7111c99b9294beadf3edd2cd3de2725b5c Mon Sep 17 00:00:00 2001 From: tmlnv Date: Sun, 19 Apr 2026 20:02:58 +0300 Subject: [PATCH] prepare plugin for obsidian community registry - rename plugin to telegram-bridge (drop 'obsidian' prefix per guidelines) - promote manifest.json + versions.json to repo root - replace settings h2/h3/h4 with Setting.setHeading() - drop plugin-name prefix from command titles, add reconnect and sign-out - wire SyncEngine polling through plugin.registerInterval - replace console.log with Notice in show-client-id - move inline usage-bar styles to CSS custom properties - gate status bar item on Platform.isDesktopApp - add first-run supabase requirement disclosure - add tag-triggered GitHub release workflow --- .github/workflows/release.yml | 59 +++++++++++++++++++++++++++++++++++ README.md | 16 ++++++---- manifest.json | 10 ++++++ package.json | 4 +-- plugin/manifest.json | 6 ++-- plugin/package.json | 4 +-- plugin/src/main.ts | 46 +++++++++++++++++++++------ plugin/src/settings-tab.ts | 34 +++++++++++++++----- plugin/src/sync-engine.ts | 11 +++++-- plugin/styles.css | 22 +++++++++++++ versions.json | 3 ++ 11 files changed, 182 insertions(+), 33 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 manifest.json create mode 100644 versions.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..c039f15 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,59 @@ +name: Release Obsidian plugin + +on: + push: + tags: + - "*.*.*" + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: plugin/package-lock.json + + - name: Install plugin dependencies + run: npm ci + working-directory: plugin + + - name: Build plugin + run: npm run build + working-directory: plugin + + - name: Verify manifest version matches tag + run: | + TAG="${GITHUB_REF_NAME}" + MANIFEST_VERSION="$(node -p "require('./manifest.json').version")" + if [ "$TAG" != "$MANIFEST_VERSION" ]; then + echo "Tag $TAG does not match manifest.json version $MANIFEST_VERSION" >&2 + exit 1 + fi + + - name: Assemble release assets + run: | + mkdir -p release + cp manifest.json release/manifest.json + cp plugin/main.js release/main.js + cp plugin/styles.css release/styles.css + + - name: Create GitHub release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.ref_name }} + name: ${{ github.ref_name }} + draft: false + prerelease: false + files: | + release/manifest.json + release/main.js + release/styles.css diff --git a/README.md b/README.md index e8b1138..e2b80c3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Obsidian Telegram Sync +# Telegram Bridge for Obsidian Sync messages from a Telegram bot into your Obsidian vault. Messages arrive in real time, are stored in Supabase, and are pulled into your vault as Markdown notes — with support for files, forum topics, message edits, and flexible routing rules. @@ -144,12 +144,12 @@ Until this plugin is published to the Obsidian community registry, install it ma ``` 2. Copy the output files into your vault's plugin folder: ``` - /.obsidian/plugins/obsidian-telegram/ - ├── main.js - ├── manifest.json - └── styles.css (if present) + /.obsidian/plugins/telegram-bridge/ + ├── main.js (from plugin/main.js) + ├── manifest.json (from repo root) + └── styles.css (from plugin/styles.css) ``` -3. In Obsidian → **Settings → Community plugins**, enable **Obsidian Telegram Sync**. +3. In Obsidian → **Settings → Community plugins**, enable **Telegram Bridge**. ### 3.2 Connect to Supabase @@ -321,7 +321,11 @@ The Telegram Bot API limits file downloads to **20 MB**. Anything larger cannot ``` obsidian-telegram/ +├── manifest.json Obsidian plugin manifest (release source of truth) +├── versions.json Plugin → min Obsidian app version map ├── plugin/ Obsidian plugin (TypeScript + esbuild) +│ ├── manifest.json Local-dev mirror of the root manifest +│ ├── styles.css Plugin styles │ └── src/ │ ├── main.ts Plugin entry point │ ├── sync-engine.ts Polling, cursor management, Realtime diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..af35639 --- /dev/null +++ b/manifest.json @@ -0,0 +1,10 @@ +{ + "id": "telegram-bridge", + "name": "Telegram Bridge", + "version": "0.1.0", + "minAppVersion": "1.5.0", + "description": "Sync messages from a Telegram bot into your vault through a self-hosted Supabase backend.", + "author": "tmlnv", + "authorUrl": "https://github.com/tmlnv/obsidian-telegram", + "isDesktopOnly": false +} diff --git a/package.json b/package.json index e0d0bba..ceddd70 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "obsidian-telegram", + "name": "telegram-bridge", "version": "0.1.0", "private": true, - "description": "Monorepo for the Obsidian Telegram plugin and Supabase backend", + "description": "Monorepo for the Telegram Bridge Obsidian plugin and Supabase backend", "scripts": { "verify": "node scripts/verify-local-env.mjs", "bootstrap:supabase": "node scripts/bootstrap-supabase.mjs", diff --git a/plugin/manifest.json b/plugin/manifest.json index d15402b..af35639 100644 --- a/plugin/manifest.json +++ b/plugin/manifest.json @@ -1,9 +1,9 @@ { - "id": "obsidian-telegram", - "name": "Obsidian Telegram", + "id": "telegram-bridge", + "name": "Telegram Bridge", "version": "0.1.0", "minAppVersion": "1.5.0", - "description": "Sync Telegram messages into Obsidian through a Supabase backend.", + "description": "Sync messages from a Telegram bot into your vault through a self-hosted Supabase backend.", "author": "tmlnv", "authorUrl": "https://github.com/tmlnv/obsidian-telegram", "isDesktopOnly": false diff --git a/plugin/package.json b/plugin/package.json index 18608d6..241184a 100644 --- a/plugin/package.json +++ b/plugin/package.json @@ -1,7 +1,7 @@ { - "name": "obsidian-telegram-plugin", + "name": "telegram-bridge-plugin", "version": "0.1.0", - "description": "Telegram to Obsidian sync plugin backed by Supabase", + "description": "Telegram Bridge Obsidian plugin backed by Supabase", "main": "main.js", "scripts": { "dev": "node esbuild.config.mjs", diff --git a/plugin/src/main.ts b/plugin/src/main.ts index eb66236..051d26b 100644 --- a/plugin/src/main.ts +++ b/plugin/src/main.ts @@ -1,4 +1,4 @@ -import { Notice, Plugin } from "obsidian"; +import { Notice, Platform, Plugin } from "obsidian"; import { createDefaultDistributionRule, DEFAULT_SETTINGS, @@ -51,8 +51,10 @@ export default class ObsidianTelegramPlugin extends Plugin { await this.saveSettings(); } - this.statusIndicator = new StatusIndicator(this); - this.statusIndicator.setIdle(); + if (Platform.isDesktopApp) { + this.statusIndicator = new StatusIndicator(this); + this.statusIndicator.setIdle(); + } this.addSettingTab(new ObsidianTelegramSettingTab(this)); @@ -62,20 +64,45 @@ export default class ObsidianTelegramPlugin extends Plugin { this.addCommand({ id: "show-client-id", - name: "Show Obsidian Telegram client ID", + name: "Show client ID", callback: () => { - this.statusIndicator?.setConnected(); - console.log("Obsidian Telegram client_id:", this.settings.client_id); + new Notice(`Client ID: ${this.settings.client_id}`); }, }); this.addCommand({ id: "sync-now", - name: "Sync Telegram messages now", + name: "Sync now", callback: () => { void this.manualSync(); }, }); + + this.addCommand({ + id: "reconnect", + name: "Reconnect", + callback: () => { + void this.reinitializeSupabase().catch((error) => { + new Notice(error instanceof Error ? error.message : String(error)); + }); + }, + }); + + this.addCommand({ + id: "sign-out", + name: "Sign out", + checkCallback: (checking) => { + if (!this.hasSession()) { + return false; + } + if (!checking) { + void this.logout().catch((error) => { + new Notice(error instanceof Error ? error.message : String(error)); + }); + } + return true; + }, + }); } onunload(): void { @@ -362,7 +389,7 @@ export default class ObsidianTelegramPlugin extends Plugin { } catch (error) { const message = error instanceof Error ? error.message : String(error); this.statusIndicator?.setError(message); - new Notice(`Obsidian Telegram initialization failed: ${message}`); + new Notice(`Telegram Bridge initialization failed: ${message}`); } } @@ -376,12 +403,13 @@ export default class ObsidianTelegramPlugin extends Plugin { vaultFingerprint: this.app.vault.getName(), pollIntervalSeconds: this.settings.poll_interval_seconds, isRealtimeEnabled: this.settings.is_realtime_enabled, + registerInterval: (id) => this.registerInterval(id), onMessages: async (messages) => { await this.processMessages(messages); }, onError: (error) => { this.statusIndicator?.setError(error.message); - console.error("Obsidian Telegram sync failed:", error); + console.error("Telegram Bridge sync failed:", error); }, }); diff --git a/plugin/src/settings-tab.ts b/plugin/src/settings-tab.ts index 06c320a..6c4cb34 100644 --- a/plugin/src/settings-tab.ts +++ b/plugin/src/settings-tab.ts @@ -15,7 +15,7 @@ export class ObsidianTelegramSettingTab extends PluginSettingTab { const { containerEl } = this; containerEl.empty(); - containerEl.createEl("h2", { text: "Obsidian Telegram" }); + this.renderIntro(containerEl); this.renderConnectionHeader(containerEl); this.renderUsageCard(containerEl); @@ -28,6 +28,22 @@ export class ObsidianTelegramSettingTab extends PluginSettingTab { this.renderAdvancedSection(containerEl); } + private renderIntro(containerEl: HTMLElement): void { + if (this.plugin.isSupabaseConfigured() && this.plugin.hasSession()) { + return; + } + + const intro = containerEl.createDiv({ cls: "obsidian-telegram-intro" }); + intro.createEl("div", { + cls: "obsidian-telegram-intro-title", + text: "Requires a self-hosted Supabase project", + }); + intro.createEl("div", { + cls: "obsidian-telegram-intro-body", + text: "This plugin reads Telegram messages from your own Supabase backend. Follow the README to provision a free Supabase project, deploy the edge functions, and create a Telegram bot before connecting below.", + }); + } + private createSection( containerEl: HTMLElement, title: string, @@ -305,10 +321,9 @@ export class ObsidianTelegramSettingTab extends PluginSettingTab { this.plugin.settings.distribution_rules.forEach((rule, index) => { const isFallbackRule = rule.filter_query.trim() === "{{all}}"; - const ruleHeader = body.createDiv({ cls: "obsidian-telegram-rule" }); - ruleHeader.createEl("h4", { - text: isFallbackRule ? `Rule ${index + 1} (fallback)` : `Rule ${index + 1}`, - }); + new Setting(body) + .setName(isFallbackRule ? `Rule ${index + 1} (fallback)` : `Rule ${index + 1}`) + .setHeading(); new Setting(body) .setName("Filter query") @@ -581,7 +596,7 @@ export class ObsidianTelegramSettingTab extends PluginSettingTab { private renderUsageCard(containerEl: HTMLElement): void { const sectionEl = containerEl.createDiv({ cls: "obsidian-telegram-usage-section" }); - sectionEl.createEl("h3", { text: "Storage usage" }); + new Setting(sectionEl).setName("Storage usage").setHeading(); const summaryEl = sectionEl.createDiv({ cls: "obsidian-telegram-usage-card" }); this.usageSummaryEl = summaryEl; @@ -706,12 +721,15 @@ export class ObsidianTelegramSettingTab extends PluginSettingTab { scaleEl.createSpan({ text: "0%" }); scaleEl.createSpan({ text: "100%" }); + const clampedUsagePercent = Math.min(100, Math.max(0, usagePercent)); + const clampedThresholdPercent = Math.min(100, Math.max(0, thresholdPercent)); + const trackEl = chartEl.createDiv({ cls: "obsidian-telegram-usage-track" }); const fillEl = trackEl.createDiv({ cls: "obsidian-telegram-usage-fill" }); - fillEl.style.width = `${Math.min(100, Math.max(0, usagePercent))}%`; + fillEl.style.setProperty("--telegram-bridge-usage-percent", `${clampedUsagePercent}%`); const thresholdEl = trackEl.createDiv({ cls: "obsidian-telegram-usage-threshold" }); - thresholdEl.style.left = `${Math.min(100, Math.max(0, thresholdPercent))}%`; + thresholdEl.style.setProperty("--telegram-bridge-threshold-percent", `${clampedThresholdPercent}%`); thresholdEl.setAttribute("aria-label", `Warning threshold ${thresholdPercent}%`); const thresholdLabelEl = chartEl.createDiv({ cls: "obsidian-telegram-usage-threshold-label" }); diff --git a/plugin/src/sync-engine.ts b/plugin/src/sync-engine.ts index a9eb2ee..0dc1eb4 100644 --- a/plugin/src/sync-engine.ts +++ b/plugin/src/sync-engine.ts @@ -10,6 +10,7 @@ export interface SyncEngineOptions { vaultFingerprint: string; pollIntervalSeconds: number; isRealtimeEnabled: boolean; + registerInterval: (id: number) => number; onMessages: (messages: MessageRow[]) => Promise; onError: (error: Error) => void; } @@ -23,6 +24,7 @@ export class SyncEngine { private readonly onError: (error: Error) => void; private readonly pollIntervalSeconds: number; private readonly isRealtimeEnabled: boolean; + private readonly registerInterval: (id: number) => number; private pollIntervalId: number | null = null; private realtimeChannel: RealtimeChannel | null = null; private isPolling = false; @@ -38,6 +40,7 @@ export class SyncEngine { this.vaultFingerprint = options.vaultFingerprint; this.pollIntervalSeconds = options.pollIntervalSeconds; this.isRealtimeEnabled = options.isRealtimeEnabled; + this.registerInterval = options.registerInterval; this.onMessages = options.onMessages; this.onError = options.onError; } @@ -47,9 +50,11 @@ export class SyncEngine { await this.poll(); this.stopPolling(); - this.pollIntervalId = window.setInterval(() => { - void this.poll(); - }, this.pollIntervalSeconds * 1000); + this.pollIntervalId = this.registerInterval( + window.setInterval(() => { + void this.poll(); + }, this.pollIntervalSeconds * 1000), + ); if (this.isRealtimeEnabled) { this.subscribeRealtime(); diff --git a/plugin/styles.css b/plugin/styles.css index 7e1f8f6..57ea218 100644 --- a/plugin/styles.css +++ b/plugin/styles.css @@ -4,6 +4,26 @@ gap: 6px; } +.obsidian-telegram-intro { + margin: 0 0 16px; + padding: 12px 14px; + border: 1px solid var(--background-modifier-border); + background: color-mix(in srgb, var(--interactive-accent) 10%, var(--background-secondary)); + border-radius: 10px; +} + +.obsidian-telegram-intro-title { + color: var(--text-normal); + font-weight: 600; + margin-bottom: 4px; +} + +.obsidian-telegram-intro-body { + color: var(--text-muted); + font-size: 0.9rem; + line-height: 1.4; +} + .obsidian-telegram-settings-header { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); @@ -128,6 +148,7 @@ .obsidian-telegram-usage-fill { position: absolute; inset: 0 auto 0 0; + width: var(--telegram-bridge-usage-percent, 0%); max-width: 100%; border-radius: inherit; background: linear-gradient(90deg, color-mix(in srgb, var(--interactive-accent) 70%, white), var(--interactive-accent)); @@ -137,6 +158,7 @@ position: absolute; top: -3px; bottom: -3px; + left: var(--telegram-bridge-threshold-percent, 0%); width: 2px; margin-left: -1px; background: var(--text-normal); diff --git a/versions.json b/versions.json new file mode 100644 index 0000000..708016d --- /dev/null +++ b/versions.json @@ -0,0 +1,3 @@ +{ + "0.1.0": "1.5.0" +}