=> {
+ const data = new TextEncoder().encode(verifier);
+ const digest = await sha256(data);
+ return base64UrlEncode(new Uint8Array(digest));
+};
diff --git a/src/assets/community-plugins.json b/src/assets/community-plugins.json
index 037458ca80..fd38595902 100644
--- a/src/assets/community-plugins.json
+++ b/src/assets/community-plugins.json
@@ -44,4 +44,4 @@
"shortDescription": "Syncs your daily study time from Super Productivity to the StudyForge leaderboard. Compete with friends, track streaks, and join 7-day study war challenges.",
"url": "https://github.com/dhananajay-030/studyforge-plugin"
}
-]
\ No newline at end of file
+]
diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json
index 596c05f007..2f00abd945 100644
--- a/src/assets/i18n/en.json
+++ b/src/assets/i18n/en.json
@@ -323,10 +323,6 @@
},
"DEFAULT_PROJECT_DESCRIPTION": "Project assigned to tasks created from issues.",
"DEFAULT_PROJECT_LABEL": "Default Super Productivity Project",
- "DIALOG": {
- "ADVANCED_CONFIG": "Advanced Config",
- "DELETE_CONFIRM": "Are you sure you want to delete this issue provider? Deleting it means that all previously imported issue tasks will loose their reference. This cannot be undone!"
- },
"HOW_TO_GET_A_TOKEN": "How to get a token?",
"ISSUE_CONTENT": {
"ASSIGNEE": "Assignee",
@@ -369,19 +365,35 @@
"PLUGIN_NOT_INSTALLED": "Required plugin \"{{pluginName}}\" is not installed.",
"POLLING_BACKLOG": "{{issueProviderName}}: Polling for new {{issuesStr}}",
"POLLING_CHANGES": "{{issueProviderName}}: Polling Changes for {{issuesStr}}",
+ "DELETE_REMOTE_FAILED": "Failed to delete remote issue",
+ "LOAD_OPTIONS_FAILED": "Failed to load options for {{fieldKey}}",
+ "OAUTH_CONNECTED": "OAuth account connected successfully.",
+ "OAUTH_DISCONNECTED": "OAuth account disconnected.",
+ "OAUTH_FAILED": "Authentication failed.",
+ "REMOTE_ISSUE_DELETED": "Remote issue was deleted. Delete linked task \"{{taskTitle}}\"?",
+ "REMOTE_ISSUE_DELETED_WITH_TIME": "Remote issue was deleted but task \"{{taskTitle}}\" has tracked time. Delete anyway?",
"TWO_WAY_SYNC_PUSH_FAILED": "Two-way sync push failed: {{errorMsg}}"
},
"TWO_WAY_SYNC": {
"AUTO_CREATE_ISSUES": "Auto-create issues for new tasks",
"AUTO_CREATE_ISSUES_DESCRIPTION": "Automatically creates an issue when you add a new top-level task to the default project. Requires a default project to be set.",
"BOTH": "Both (bidirectional)",
+ "DUE_DAY": "Due Day",
+ "DUE_WITH_TIME": "Due Date/Time",
"NOTES": "Notes / Description sync direction",
"OFF": "Off",
"PULL_ONLY": "Pull only",
"PUSH_ONLY": "Push only",
"SECTION": "Two-Way Sync (experimental)",
"STATUS": "Status sync direction",
+ "TIME_ESTIMATE": "Time Estimate",
"TITLE": "Title sync direction"
+ },
+ "DIALOG": {
+ "ADVANCED_CONFIG": "Advanced Config",
+ "CONNECTED": "Connected",
+ "DELETE_CONFIRM": "Are you sure you want to delete this issue provider? Deleting it means that all previously imported issue tasks will loose their reference. This cannot be undone!",
+ "DISCONNECT": "Disconnect"
}
},
"ISSUE_PANEL": {
diff --git a/src/assets/oauth-callback.html b/src/assets/oauth-callback.html
new file mode 100644
index 0000000000..8f45e1b07a
--- /dev/null
+++ b/src/assets/oauth-callback.html
@@ -0,0 +1,25 @@
+
+
+
+
+ Authorization Complete
+
+
+ Authorization complete. You can close this window.
+
+
+
diff --git a/src/main.ts b/src/main.ts
index 5a0cf85ae6..21a100d38c 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -75,6 +75,8 @@ import { PLUGIN_INITIALIZER_PROVIDER } from './app/plugins/plugin-initializer';
import { initializeMatMenuTouchFix } from './app/features/tasks/task-context-menu/mat-menu-touch-monkey-patch';
import { Log } from './app/core/log';
import { OperationWriteFlushService } from './app/op-log/sync/operation-write-flush.service';
+import { PluginOAuthRedirectHandler } from './app/plugins/oauth/plugin-oauth-redirect.handler';
+import { OAuthCallbackHandlerService } from './app/imex/sync/oauth-callback-handler.service';
import { GlobalConfigService } from './app/features/config/global-config.service';
import { LocaleDatePipe } from './app/ui/pipes/locale-date.pipe';
import { DateTimeFormatService } from './app/core/date-time-format/date-time-format.service';
@@ -227,7 +229,7 @@ bootstrapApplication(AppComponent, {
multi: true,
},
// Ensure DataInitService is instantiated at bootstrap.
- // Its constructor triggers reInit() → hydrateStore() → loadAllData into NgRx.
+ // Its constructor triggers reInit() -> hydrateStore() -> loadAllData into NgRx.
{
provide: APP_INITIALIZER,
useFactory: (_dataInit: DataInitService) => {
@@ -246,6 +248,28 @@ bootstrapApplication(AppComponent, {
deps: [EncryptionPasswordDialogOpenerService],
multi: true,
},
+ // Ensure PluginOAuthRedirectHandler is instantiated at bootstrap.
+ // Its constructor registers platform-specific listeners (postMessage / Electron IPC)
+ // that bridge OAuth redirect callbacks to PluginOAuthService.
+ {
+ provide: APP_INITIALIZER,
+ useFactory: (_handler: PluginOAuthRedirectHandler) => {
+ return () => {};
+ },
+ deps: [PluginOAuthRedirectHandler],
+ multi: true,
+ },
+ // Ensure OAuthCallbackHandlerService is instantiated at bootstrap on native platforms.
+ // Its constructor registers Capacitor's appUrlOpen listener that bridges
+ // both Dropbox and plugin OAuth redirect callbacks.
+ {
+ provide: APP_INITIALIZER,
+ useFactory: (_handler: OAuthCallbackHandlerService) => {
+ return () => {};
+ },
+ deps: [OAuthCallbackHandlerService],
+ multi: true,
+ },
// Note: ImmediateUploadService now initializes itself in constructor
// after DataInitStateService.isAllDataLoadedInitially$ fires to avoid
// race condition where upload attempts happen before sync config is loaded