Skip to content

Commit 2e630e5

Browse files
committed
remove style css and update setting to use only secret storage
1 parent 289c9ce commit 2e630e5

7 files changed

Lines changed: 162 additions & 259 deletions

File tree

.github/workflows/release.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,3 @@ jobs:
4848
files: |
4949
main.js
5050
manifest.json
51-
styles.css

README.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ Works with plain markdown checkboxes. Compatible with the [Tasks](https://publis
99
1. Install the plugin and enable it in **Settings > Community plugins**.
1010
2. Go to **Settings > Beeminder Task Sync**.
1111
3. Click **Open** to get your auth token from Beeminder.
12-
4. Paste the token into the **Auth token** field.
12+
4. In the **Auth token** setting, create or select a secret in Obsidian SecretStorage that contains your Beeminder auth token.
1313
5. Click **Validate** — this confirms the connection and caches your goal list for autocomplete.
1414

15-
The token is stored in Obsidian's secret storage when available, with a localStorage fallback on older versions.
15+
The plugin stores only a reference to your selected secret in its settings. The token value itself is read from Obsidian SecretStorage.
1616

1717
## Usage
1818

@@ -58,7 +58,7 @@ npm install
5858
npm run build
5959
```
6060

61-
Copy `main.js`, `manifest.json`, and `styles.css` into `.obsidian/plugins/beeminder-sync/` in your vault.
61+
Copy `main.js` and `manifest.json` into `.obsidian/plugins/beeminder-sync/` in your vault.
6262

6363
To build and install directly into a vault:
6464

@@ -83,6 +83,5 @@ When that tag is pushed, GitHub Actions builds the plugin and creates a release
8383

8484
- `main.js`
8585
- `manifest.json`
86-
- `styles.css`
8786

8887
The workflow fails if the tag does not exactly match `manifest.json`'s `version`, which is the release format Obsidian expects.

manifest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"id": "beeminder-sync",
33
"name": "Beeminder Task Sync",
4-
"version": "1.0.0",
5-
"minAppVersion": "1.11.0",
4+
"version": "1.0.1",
5+
"minAppVersion": "1.11.4",
66
"description": "Sync task completions to Beeminder goals. Works with the Tasks plugin.",
77
"author": "Leon Staufer",
88
"isDesktopOnly": false

scripts/install-plugin.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ async function main() {
3535
}
3636

3737
const pluginDir = path.join(vaultPath, ".obsidian", "plugins", manifest.id);
38-
const filesToCopy = ["main.js", "manifest.json", "styles.css"];
38+
const filesToCopy = ["main.js", "manifest.json"];
3939

4040
await fs.mkdir(pluginDir, { recursive: true });
4141

src/main.ts

Lines changed: 9 additions & 197 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import {
22
Notice,
33
Plugin,
4-
PluginSettingTab,
5-
Setting,
6-
App,
7-
MarkdownView,
84
TAbstractFile,
95
TFile,
106
} from "obsidian";
117
import { BeeminderApi } from "./beeminder-api";
8+
import {
9+
BeeminderSyncSettingTab,
10+
type BeeminderSyncSettings,
11+
type SyncedDatapoint,
12+
DEFAULT_SETTINGS,
13+
} from "./settings";
1214
import { BeeminderSuggest } from "./suggest";
1315

1416
// Regex to match the 🐝 annotation in a task line
@@ -22,30 +24,6 @@ const TASK_LINE_REGEX = /^(\s*)([-*+]|\d+[.)]) \[([^\]])\]\s+(.*)$/u;
2224
const TASKS_TRAILING_METADATA_REGEX =
2325
/\s(?=(?:#\S+|(?:🔺||🔼|🔽||🛫|||📅|||🔁|🏁||🆔)\b))/u;
2426

25-
interface SyncedDatapoint {
26-
goalSlug: string;
27-
datapointId: string;
28-
requestId: string;
29-
}
30-
31-
interface BeeminderSyncSettings {
32-
username: string;
33-
tokenStorageKey: string;
34-
cachedGoals: { slug: string; title?: string }[];
35-
autocompleteMinMatchLength: number;
36-
showNotifications: boolean;
37-
syncedDatapoints: Record<string, SyncedDatapoint>;
38-
}
39-
40-
const DEFAULT_SETTINGS: BeeminderSyncSettings = {
41-
username: "",
42-
tokenStorageKey: "beeminder-auth-token",
43-
cachedGoals: [],
44-
autocompleteMinMatchLength: 1,
45-
showNotifications: true,
46-
syncedDatapoints: {},
47-
};
48-
4927
interface ParsedTask {
5028
lineNumber: number;
5129
line: string;
@@ -58,38 +36,6 @@ interface FileSnapshot {
5836
tasks: Map<number, ParsedTask>;
5937
}
6038

61-
interface UsageTip {
62-
title: string;
63-
body: string;
64-
code?: string;
65-
}
66-
67-
const USAGE_TIPS: UsageTip[] = [
68-
{
69-
title: "Basic",
70-
body: "Add a 🐝 annotation to any task to send a datapoint when that task is completed.",
71-
code: "- [ ] Read chapter 5 🐝 reading",
72-
},
73-
{
74-
title: "Custom value",
75-
body: "Use =number to send a custom value instead of the default +1.",
76-
code: "- [ ] Run 5km 🐝 exercise=5",
77-
},
78-
{
79-
title: "With Tasks metadata",
80-
body: "Place 🐝 before trailing Tasks metadata so the annotation is parsed correctly.",
81-
code: "- [ ] Write post 🐝 words=500 📅 2026-04-10",
82-
},
83-
{
84-
title: "Autocomplete",
85-
body: 'Type "🐝", "bee", or "goal" on a task line to get suggestions from your cached goals.',
86-
},
87-
{
88-
title: "Unsync",
89-
body: "Unchecking a synced task removes the corresponding datapoint from Beeminder.",
90-
},
91-
];
92-
9339
function parseTaskLine(line: string, lineNumber: number): ParsedTask | null {
9440
const match = line.match(TASK_LINE_REGEX);
9541
if (!match) return null;
@@ -219,33 +165,11 @@ export default class BeeminderSyncPlugin extends Plugin {
219165
this.fileSnapshots.clear();
220166
}
221167

222-
// --- Token storage (secret storage with localStorage fallback) ---
223-
224-
async storeToken(token: string): Promise<void> {
225-
if ((this.app as any).secretStorage) {
226-
(this.app as any).secretStorage.setSecret(this.settings.tokenStorageKey, token);
227-
return;
228-
}
229-
this.app.saveLocalStorage(this.getLegacyTokenKey(), token);
230-
}
231-
232-
async clearToken(): Promise<void> {
233-
if ((this.app as any).secretStorage) {
234-
(this.app as any).secretStorage.setSecret(this.settings.tokenStorageKey, "");
235-
}
236-
this.app.saveLocalStorage(this.getLegacyTokenKey(), null as any);
237-
}
168+
// --- Token storage ---
238169

239170
async getToken(): Promise<string | null> {
240-
if ((this.app as any).secretStorage) {
241-
const token = (this.app as any).secretStorage.getSecret(this.settings.tokenStorageKey);
242-
if (token) return token;
243-
}
244-
return this.app.loadLocalStorage(this.getLegacyTokenKey());
245-
}
246-
247-
private getLegacyTokenKey(): string {
248-
return `${this.manifest.id}:${this.settings.tokenStorageKey}`;
171+
if (!this.settings.tokenSecretId) return null;
172+
return this.app.secretStorage.getSecret(this.settings.tokenSecretId);
249173
}
250174

251175
// --- Goal management ---
@@ -484,115 +408,3 @@ export default class BeeminderSyncPlugin extends Plugin {
484408
await this.saveData(this.settings);
485409
}
486410
}
487-
488-
class BeeminderSyncSettingTab extends PluginSettingTab {
489-
plugin: BeeminderSyncPlugin;
490-
491-
constructor(app: App, plugin: BeeminderSyncPlugin) {
492-
super(app, plugin);
493-
this.plugin = plugin;
494-
}
495-
496-
display(): void {
497-
const { containerEl } = this;
498-
containerEl.empty();
499-
500-
new Setting(containerEl)
501-
.setName("Open Beeminder")
502-
.setDesc("Get your auth token from Beeminder.")
503-
.addButton((btn) => {
504-
btn.setButtonText("Open").onClick(() => {
505-
window.open("https://www.beeminder.com/api/v1/auth_token.json", "_blank", "noopener,noreferrer");
506-
});
507-
});
508-
509-
new Setting(containerEl)
510-
.setName("Auth token")
511-
.setDesc("Stored in Obsidian secret storage when available.")
512-
.addText((text) => {
513-
text.setPlaceholder("Paste your Beeminder auth token");
514-
text.onChange(async (value) => {
515-
if (!value.trim()) return;
516-
await this.plugin.storeToken(value.trim());
517-
text.setValue("");
518-
new Notice("Token saved.");
519-
});
520-
})
521-
.addButton((btn) => {
522-
btn.setButtonText("Clear").onClick(async () => {
523-
await this.plugin.clearToken();
524-
new Notice("Token cleared.");
525-
});
526-
});
527-
528-
new Setting(containerEl)
529-
.setName("Username")
530-
.setDesc("Auto-filled after token validation.")
531-
.addText((text) =>
532-
text
533-
.setPlaceholder("username")
534-
.setValue(this.plugin.settings.username)
535-
.onChange(async (value) => {
536-
this.plugin.settings.username = value.trim();
537-
await this.plugin.saveSettings();
538-
})
539-
);
540-
541-
new Setting(containerEl)
542-
.setName("Validate token & refresh goals")
543-
.setDesc("Checks the token and caches your goal list for autocomplete.")
544-
.addButton((btn) => {
545-
btn.setButtonText("Validate").setCta().onClick(async () => {
546-
btn.setDisabled(true);
547-
try {
548-
await this.plugin.validateAndRefreshGoals();
549-
new Notice(`Connected as ${this.plugin.settings.username} (${this.plugin.settings.cachedGoals.length} goals)`);
550-
this.display();
551-
} catch (e) {
552-
new Notice(e instanceof Error ? e.message : "Validation failed.");
553-
} finally {
554-
btn.setDisabled(false);
555-
}
556-
});
557-
});
558-
559-
new Setting(containerEl)
560-
.setName("Autocomplete minimum match length")
561-
.setDesc("How many typed characters are required before text-based goal suggestions appear. Set to 0 to show suggestions immediately on task lines.")
562-
.addText((text) =>
563-
text
564-
.setPlaceholder("1")
565-
.setValue(String(this.plugin.settings.autocompleteMinMatchLength))
566-
.onChange(async (value) => {
567-
const parsed = Number.parseInt(value, 10);
568-
this.plugin.settings.autocompleteMinMatchLength =
569-
Number.isInteger(parsed) && parsed >= 0 ? parsed : DEFAULT_SETTINGS.autocompleteMinMatchLength;
570-
await this.plugin.saveSettings();
571-
})
572-
);
573-
574-
new Setting(containerEl)
575-
.setName("Show notifications")
576-
.setDesc("Show a notice when datapoints are synced or removed.")
577-
.addToggle((toggle) =>
578-
toggle
579-
.setValue(this.plugin.settings.showNotifications)
580-
.onChange(async (value) => {
581-
this.plugin.settings.showNotifications = value;
582-
await this.plugin.saveSettings();
583-
})
584-
);
585-
586-
containerEl.createEl("h3", { text: "Usage" });
587-
const instructions = containerEl.createDiv({ cls: "beeminder-settings-help" });
588-
589-
for (const tip of USAGE_TIPS) {
590-
const tipEl = instructions.createDiv({ cls: "beeminder-settings-help-tip" });
591-
tipEl.createEl("strong", { text: tip.title });
592-
tipEl.createEl("p", { text: tip.body });
593-
if (tip.code) {
594-
tipEl.createEl("code", { text: tip.code });
595-
}
596-
}
597-
}
598-
}

0 commit comments

Comments
 (0)