diff --git a/callback.html b/callback.html index 0116c45..69ed512 100644 --- a/callback.html +++ b/callback.html @@ -7,19 +7,29 @@

Authorization Complete

Processing...

+ - \ No newline at end of file + diff --git a/index.html b/index.html index a7838a3..69ed512 100644 --- a/index.html +++ b/index.html @@ -7,19 +7,29 @@

Authorization Complete

Processing...

+ - \ No newline at end of file + diff --git a/main.js b/main.js index 6720bc2..4ddbba9 100644 --- a/main.js +++ b/main.js @@ -3,10 +3,8 @@ THIS IS A GENERATED/BUNDLED FILE BY ESBUILD if you want to view the source, please visit the github repository of this plugin */ -var f=Object.defineProperty;var v=Object.getOwnPropertyDescriptor;var E=Object.getOwnPropertyNames;var _=Object.prototype.hasOwnProperty;var U=(r,e)=>{for(var t in e)f(r,t,{get:e[t],enumerable:!0})},A=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of E(e))!_.call(r,s)&&s!==t&&f(r,s,{get:()=>e[s],enumerable:!(n=v(e,s))||n.enumerable});return r};var I=r=>A(f({},"__esModule",{value:!0}),r);var j={};U(j,{default:()=>k});module.exports=I(j);var y=require("obsidian"),o=require("obsidian");var c=require("obsidian"),w={accessToken:"",clientId:"",clientSecret:"",redirectUri:"https://ticktick-quick-add-obsidian-6yawfmvnj-mooshs-projects-0635287d.vercel.app",selectionMode:"line",tagPosition:"append"},u=class extends c.PluginSettingTab{constructor(e,t){super(e,t),this.plugin=t}display(){let{containerEl:e}=this;e.empty(),new c.Setting(e).setName("Client id").setDesc("Enter your TickTick Client ID from the Developer Console.").addText(t=>t.setPlaceholder("Your Client ID").setValue(this.plugin.settings.clientId||"").onChange(async n=>{this.plugin.settings.clientId=n.trim(),await this.plugin.saveSettings()})),new c.Setting(e).setName("Client secret").setDesc("Enter your TickTick Client Secret (input is masked).").addText(t=>(t.inputEl.type="password",t.setPlaceholder("\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022").setValue(this.plugin.settings.clientSecret||"").onChange(async n=>{this.plugin.settings.clientSecret=n.trim(),await this.plugin.saveSettings()}),t)),new c.Setting(e).setName("Redirect URI").setDesc("Enter the Redirect URI registered in your TickTick Developer Portal. (Default: https://ticktick-quick-add-obsidian-6yawfmvnj-mooshs-projects-0635287d.vercel.app)").addText(t=>t.setPlaceholder("https://ticktick-quick-add-obsidian-6yawfmvnj-mooshs-projects-0635287d.vercel.app").setValue(this.plugin.settings.redirectUri||"").onChange(async n=>{this.plugin.settings.redirectUri=n.trim(),await this.plugin.saveSettings()})),new c.Setting(e).setName("Selection mode").setDesc("Choose whether the command captures only the current line or the entire paragraph.").addDropdown(t=>t.addOption("line","Current line").addOption("paragraph","Entire paragraph").setValue(this.plugin.settings.selectionMode).onChange(async n=>{this.plugin.settings.selectionMode=n,await this.plugin.saveSettings()})),new c.Setting(e).setName("Tag position").setDesc("Choose whether the #ticktick tag is added to the beginning or end of the text.").addDropdown(t=>t.addOption("append","Append (end)").addOption("prepend","Prepend (beginning)").setValue(this.plugin.settings.tagPosition).onChange(async n=>{this.plugin.settings.tagPosition=n,await this.plugin.saveSettings()})),new c.Setting(e).setName("Access token").setDesc("Your current access token (read-only).").addText(t=>t.setPlaceholder("Access token will appear here").setValue(this.plugin.settings.accessToken||"").onChange(async n=>{this.plugin.settings.accessToken=n.trim(),await this.plugin.saveSettings()})),new c.Setting(e).setName("Refresh token").setDesc("Stored refresh token (for debugging purposes).").addText(t=>t.setPlaceholder("Refresh token").setValue(this.plugin.settings.refreshToken||"").onChange(async n=>{this.plugin.settings.refreshToken=n.trim(),await this.plugin.saveSettings()})),new c.Setting(e).setName("Connect to TickTick").setDesc("Click to open the OAuth authorization URL in your browser.").addButton(async t=>{t.setButtonText("Connect").onClick(async()=>{await this.plugin.startAuthFlow()})}),new c.Setting(e).setName("Authorization code").setDesc("Paste the authorization code from TickTick here to obtain an access token.").addText(t=>t.setPlaceholder("Enter authorization code").onChange(async n=>{n.trim()&&(await this.plugin.exchangeAuthCodeForToken(n.trim()),t.setValue(""),this.display())}))}};function m(r){let e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",t="";for(let n=0;n0&&r.getLine(t-1).trim()!=="";)t--;for(;n{let n=e.getCursor();if(!t.file){new o.Notice("No file found for the current view!");return}let s,a,i;if(this.settings.selectionMode==="paragraph"){let{text:h,start:p,end:P}=N(e,n.line);s=h,a=p,i=P}else{let{text:h,line:p}=D(e,n.line);s=h,a=p,i=p}if(!s.trim()){new o.Notice("No text found on the current line!");return}let d=m(8),l=this.settings.tagPosition==="prepend"?`#ticktick ${s} ^${d}`:`${s} #ticktick ^${d}`;e.replaceRange(l,{line:a,ch:0},{line:i,ch:e.getLine(i).length});let g=this.app.vault.getName(),S=t.file.path,x=`obsidian://advanced-uri?vault=${encodeURIComponent(g)}&filepath=${encodeURIComponent(S)}&block=${encodeURIComponent(d)}`,C=`${s} +var f=Object.defineProperty;var v=Object.getOwnPropertyDescriptor;var _=Object.getOwnPropertyNames;var E=Object.prototype.hasOwnProperty;var A=(r,e)=>{for(var t in e)f(r,t,{get:e[t],enumerable:!0})},I=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of _(e))!E.call(r,s)&&s!==t&&f(r,s,{get:()=>e[s],enumerable:!(n=v(e,s))||n.enumerable});return r};var U=r=>I(f({},"__esModule",{value:!0}),r);var j={};A(j,{default:()=>k});module.exports=U(j);var y=require("obsidian"),o=require("obsidian");var c=require("obsidian"),w={accessToken:"",clientId:"",clientSecret:"",redirectUri:"https://ticktick-quick-add-obsidian-6yawfmvnj-mooshs-projects-0635287d.vercel.app",selectionMode:"line",tagPosition:"append"},u=class extends c.PluginSettingTab{constructor(e,t){super(e,t),this.plugin=t}display(){let{containerEl:e}=this;e.empty(),new c.Setting(e).setName("Client id").setDesc("Enter your TickTick Client ID from the Developer Console.").addText(t=>t.setPlaceholder("Your Client ID").setValue(this.plugin.settings.clientId||"").onChange(async n=>{this.plugin.settings.clientId=n.trim(),await this.plugin.saveSettings()})),new c.Setting(e).setName("Client secret").setDesc("Enter your TickTick Client Secret (input is masked).").addText(t=>(t.inputEl.type="password",t.setPlaceholder("\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022").setValue(this.plugin.settings.clientSecret||"").onChange(async n=>{this.plugin.settings.clientSecret=n.trim(),await this.plugin.saveSettings()}),t)),new c.Setting(e).setName("Redirect URI").setDesc("Enter the Redirect URI registered in your TickTick Developer Portal. (Default: https://ticktick-quick-add-obsidian-6yawfmvnj-mooshs-projects-0635287d.vercel.app)").addText(t=>t.setPlaceholder("https://ticktick-quick-add-obsidian-6yawfmvnj-mooshs-projects-0635287d.vercel.app").setValue(this.plugin.settings.redirectUri||"").onChange(async n=>{this.plugin.settings.redirectUri=n.trim(),await this.plugin.saveSettings()})),new c.Setting(e).setName("Selection mode").setDesc("Choose whether the command captures only the current line or the entire paragraph.").addDropdown(t=>t.addOption("line","Current line").addOption("paragraph","Entire paragraph").setValue(this.plugin.settings.selectionMode).onChange(async n=>{this.plugin.settings.selectionMode=n,await this.plugin.saveSettings()})),new c.Setting(e).setName("Tag position").setDesc("Choose whether the #ticktick tag is added to the beginning or end of the text.").addDropdown(t=>t.addOption("append","Append (end)").addOption("prepend","Prepend (beginning)").setValue(this.plugin.settings.tagPosition).onChange(async n=>{this.plugin.settings.tagPosition=n,await this.plugin.saveSettings()})),new c.Setting(e).setName("Access token").setDesc("Your current access token (read-only).").addText(t=>t.setPlaceholder("Access token will appear here").setValue(this.plugin.settings.accessToken||"").onChange(async n=>{this.plugin.settings.accessToken=n.trim(),await this.plugin.saveSettings()})),new c.Setting(e).setName("Refresh token").setDesc("Stored refresh token (for debugging purposes).").addText(t=>t.setPlaceholder("Refresh token").setValue(this.plugin.settings.refreshToken||"").onChange(async n=>{this.plugin.settings.refreshToken=n.trim(),await this.plugin.saveSettings()})),new c.Setting(e).setName("Connect to TickTick").setDesc("Opens the OAuth authorization page in your browser. After approving, you will be returned to Obsidian automatically.").addButton(async t=>{t.setButtonText("Connect").onClick(async()=>{await this.plugin.startAuthFlow()})})}};function m(r){let e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",t="";for(let n=0;n0&&r.getLine(t-1).trim()!=="";)t--;for(;n{if(!e.code){new o.Notice("TickTick auth failed: no authorization code returned.");return}if(!this.settings.tempState||e.state!==this.settings.tempState){new o.Notice("TickTick auth failed: state mismatch. Please try connecting again.");return}await this.exchangeAuthCodeForToken(e.code)}),this.addCommand({id:"create-ticktick-task",name:"Create TickTick task",editorCallback:async(e,t)=>{let n=e.getCursor();if(!t.file){new o.Notice("No file found for the current view!");return}let s,a,i;if(this.settings.selectionMode==="paragraph"){let{text:g,start:p,end:P}=N(e,n.line);s=g,a=p,i=P}else{let{text:g,line:p}=D(e,n.line);s=g,a=p,i=p}if(!s.trim()){new o.Notice("No text found on the current line!");return}let d=m(8),l=this.settings.tagPosition==="prepend"?`#ticktick ${s} ^${d}`:`${s} #ticktick ^${d}`;e.replaceRange(l,{line:a,ch:0},{line:i,ch:e.getLine(i).length});let h=this.app.vault.getName(),S=t.file.path,b=`obsidian://advanced-uri?vault=${encodeURIComponent(h)}&filepath=${encodeURIComponent(S)}&block=${encodeURIComponent(d)}`,x=`${s} -[Open in Obsidian](${x})`,b=s.length>50?s.substring(0,50)+"...":s;await this.ensureFreshToken();try{(await this.createTicktickTask(b,C)).success?new o.Notice("TickTick task created successfully!"):new o.Notice("Failed to create TickTick task.")}catch(h){T(this,"task_creation_failed",{error:h.message}),new o.Notice(`Failed to create task: ${h.message} -Check console for details.`)}}})}onunload(){console.log("Unloading TickTick Plugin")}async startAuthFlow(){let e=this.settings.clientId;if(!e){new o.Notice("Please enter your Client ID in the settings.");return}let t=this.settings.redirectUri||"https://ticktick-quick-add-obsidian-6yawfmvnj-mooshs-projects-0635287d.vercel.app",n=encodeURIComponent("tasks:read tasks:write"),{codeVerifier:s,codeChallenge:a}=await R(),i=m(32);this.settings.tempCodeVerifier=s,this.settings.tempState=i,await this.saveSettings();let d=`https://ticktick.com/oauth/authorize?client_id=${e}&redirect_uri=${encodeURIComponent(t)}&response_type=code&scope=${n}&code_challenge=${a}&code_challenge_method=S256&state=${i}`;window.open(d,"_blank"),new o.Notice("OAuth flow initiated. Please complete it in your browser.")}async exchangeAuthCodeForToken(e){let t="https://ticktick.com/oauth/token",n=this.settings.redirectUri||"https://ticktick-quick-add-obsidian-6yawfmvnj-mooshs-projects-0635287d.vercel.app",s=this.settings.clientId,a=this.settings.clientSecret,i=this.settings.tempCodeVerifier;if(!i||!s||!a){new o.Notice("Missing required credentials. Please update your settings.");return}let d=new URLSearchParams({grant_type:"authorization_code",code:e,redirect_uri:n,client_id:s,client_secret:a,code_verifier:i});try{let l=await(0,o.requestUrl)({url:t,method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64)"},body:d.toString()});if(l.status===200){let g=l.json;g.refresh_token?this.settings.refreshToken=g.refresh_token:(console.warn("Refresh token not received during token exchange."),new o.Notice("Refresh token not received. Re-authentication may be required more often.")),this.settings.accessToken=g.access_token,this.settings.tokenExpiry=Date.now()+g.expires_in*1e3*.85,await this.saveSettings(),new o.Notice("TickTick access token obtained successfully!")}else console.error("Token exchange error (status):",l.status),console.log("response.json:",l.json),console.log("response.text:",l.text),new o.Notice("Failed to obtain access token.")}catch(l){console.error("Error during token exchange:",l),new o.Notice("Error during token exchange.")}}async refreshAccessToken(){if(!this.settings.refreshToken)throw new Error("No refresh token available. Please reconnect.");let e="https://ticktick.com/oauth/token",t=this.settings.clientId,n=this.settings.clientSecret,s=new URLSearchParams({grant_type:"refresh_token",refresh_token:this.settings.refreshToken,client_id:t,client_secret:n});try{let a=await(0,o.requestUrl)({url:e,method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64)"},body:s.toString()});if(a.status===200){let i=a.json;i.refresh_token&&(this.settings.refreshToken=i.refresh_token),this.settings.accessToken=i.access_token,this.settings.tokenExpiry=Date.now()+i.expires_in*1e3*.85,await this.saveSettings(),new o.Notice("Access token refreshed successfully!")}else throw console.error("Refresh token error:",a.text),new Error("Failed to refresh token")}catch(a){throw console.error("Error refreshing access token:",a),a}}async ensureFreshToken(){if(this.settings.tokenExpiry&&Date.now()>this.settings.tokenExpiry){T(this,"token_expired",{tokenExpiry:this.settings.tokenExpiry});try{await this.refreshAccessToken()}catch(e){throw new o.Notice("Reauthentication required. Please reconnect to TickTick."),e}}}async createTicktickTask(e,t){let n=this.settings.accessToken;if(!n)return new o.Notice("No access token found. Please connect to TickTick in the settings."),{success:!1};let s="https://api.ticktick.com/open/v1/task",a={title:e,content:t};try{T(this,"task_creation_request",{endpoint:s,title:e});let i=await(0,o.requestUrl)({url:s,method:"POST",headers:{Authorization:`Bearer ${n}`,"Content-Type":"application/json","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64)"},body:JSON.stringify(a)});return i.status===200?{success:!0}:(console.error("TickTick API error (status):",i.status),console.log("response.json:",i.json),console.log("response.text:",i.text),{success:!1})}catch(i){return console.error("TickTick API error:",i),{success:!1}}}async loadSettings(){this.settings=Object.assign({},w,await this.loadData())}async saveSettings(){await this.saveData(this.settings)}}; - -/* nosourcemap */ \ No newline at end of file +[Open in Obsidian](${b})`,C=s.length>50?s.substring(0,50)+"...":s;await this.ensureFreshToken();try{(await this.createTicktickTask(C,x)).success?new o.Notice("TickTick task created successfully!"):new o.Notice("Failed to create TickTick task.")}catch(g){T(this,"task_creation_failed",{error:g.message}),new o.Notice(`Failed to create task: ${g.message} +Check console for details.`)}}})}onunload(){console.log("Unloading TickTick Plugin")}async startAuthFlow(){let e=this.settings.clientId;if(!e){new o.Notice("Please enter your Client ID in the settings.");return}let t=this.settings.redirectUri||"https://ticktick-quick-add-obsidian-6yawfmvnj-mooshs-projects-0635287d.vercel.app",n=encodeURIComponent("tasks:read tasks:write"),{codeVerifier:s,codeChallenge:a}=await R(),i=m(32);this.settings.tempCodeVerifier=s,this.settings.tempState=i,await this.saveSettings();let d=`https://ticktick.com/oauth/authorize?client_id=${e}&redirect_uri=${encodeURIComponent(t)}&response_type=code&scope=${n}&code_challenge=${a}&code_challenge_method=S256&state=${i}`;window.open(d,"_blank"),new o.Notice("OAuth flow initiated. Please complete it in your browser.")}async exchangeAuthCodeForToken(e){let t="https://ticktick.com/oauth/token",n=this.settings.redirectUri||"https://ticktick-quick-add-obsidian-6yawfmvnj-mooshs-projects-0635287d.vercel.app",s=this.settings.clientId,a=this.settings.clientSecret,i=this.settings.tempCodeVerifier;if(!i||!s||!a){new o.Notice("Missing required credentials. Please update your settings.");return}let d=new URLSearchParams({grant_type:"authorization_code",code:e,redirect_uri:n,client_id:s,client_secret:a,code_verifier:i});try{let l=await(0,o.requestUrl)({url:t,method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64)"},body:d.toString()});if(l.status===200){let h=l.json;h.refresh_token?this.settings.refreshToken=h.refresh_token:(console.warn("Refresh token not received during token exchange."),new o.Notice("Refresh token not received. Re-authentication may be required more often.")),this.settings.accessToken=h.access_token,this.settings.tokenExpiry=Date.now()+h.expires_in*1e3*.85,this.settings.tempCodeVerifier=void 0,this.settings.tempState=void 0,await this.saveSettings(),new o.Notice("TickTick access token obtained successfully!")}else console.error("Token exchange error (status):",l.status),console.log("response.json:",l.json),console.log("response.text:",l.text),new o.Notice("Failed to obtain access token.")}catch(l){console.error("Error during token exchange:",l),new o.Notice("Error during token exchange.")}}async refreshAccessToken(){if(!this.settings.refreshToken)throw new Error("No refresh token available. Please reconnect.");let e="https://ticktick.com/oauth/token",t=this.settings.clientId,n=this.settings.clientSecret,s=new URLSearchParams({grant_type:"refresh_token",refresh_token:this.settings.refreshToken,client_id:t,client_secret:n});try{let a=await(0,o.requestUrl)({url:e,method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64)"},body:s.toString()});if(a.status===200){let i=a.json;i.refresh_token&&(this.settings.refreshToken=i.refresh_token),this.settings.accessToken=i.access_token,this.settings.tokenExpiry=Date.now()+i.expires_in*1e3*.85,await this.saveSettings(),new o.Notice("Access token refreshed successfully!")}else throw console.error("Refresh token error:",a.text),new Error("Failed to refresh token")}catch(a){throw console.error("Error refreshing access token:",a),a}}async ensureFreshToken(){if(this.settings.tokenExpiry&&Date.now()>this.settings.tokenExpiry){T(this,"token_expired",{tokenExpiry:this.settings.tokenExpiry});try{await this.refreshAccessToken()}catch(e){throw new o.Notice("Reauthentication required. Please reconnect to TickTick."),e}}}async createTicktickTask(e,t){let n=this.settings.accessToken;if(!n)return new o.Notice("No access token found. Please connect to TickTick in the settings."),{success:!1};let s="https://api.ticktick.com/open/v1/task",a={title:e,content:t};try{T(this,"task_creation_request",{endpoint:s,title:e});let i=await(0,o.requestUrl)({url:s,method:"POST",headers:{Authorization:`Bearer ${n}`,"Content-Type":"application/json","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64)"},body:JSON.stringify(a)});return i.status===200?{success:!0}:(console.error("TickTick API error (status):",i.status),console.log("response.json:",i.json),console.log("response.text:",i.text),{success:!1})}catch(i){return console.error("TickTick API error:",i),{success:!1}}}async loadSettings(){this.settings=Object.assign({},w,await this.loadData())}async saveSettings(){await this.saveData(this.settings)}}; diff --git a/main.ts b/main.ts index 55bca9d..d080f41 100644 --- a/main.ts +++ b/main.ts @@ -83,6 +83,18 @@ export default class TickTickPlugin extends Plugin { await this.loadSettings(); this.addSettingTab(new TickTickSettingTab(this.app, this)); + this.registerObsidianProtocolHandler('ticktick-callback', async (params) => { + if (!params.code) { + new Notice('TickTick auth failed: no authorization code returned.'); + return; + } + if (!this.settings.tempState || params.state !== this.settings.tempState) { + new Notice('TickTick auth failed: state mismatch. Please try connecting again.'); + return; + } + await this.exchangeAuthCodeForToken(params.code); + }); + // Command: Create TickTick task this.addCommand({ id: 'create-ticktick-task', @@ -218,6 +230,8 @@ export default class TickTickPlugin extends Plugin { this.settings.accessToken = data.access_token; this.settings.tokenExpiry = Date.now() + (data.expires_in * 1000 * 0.85); + this.settings.tempCodeVerifier = undefined; + this.settings.tempState = undefined; await this.saveSettings(); new Notice('TickTick access token obtained successfully!'); } else { diff --git a/manifest.json b/manifest.json index 97c284e..85960a1 100644 --- a/manifest.json +++ b/manifest.json @@ -6,6 +6,6 @@ "description": "Create TickTick tasks from text blocks with automatic Obsidian URI links. Requires Advanced URI plugin.", "author": "Muxin Li", "authorUrl": "https://github.com/heymoosh", - "isDesktopOnly": true, + "isDesktopOnly": false, "main": "main.js" } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e1a6f8a..4f4ff8c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-sample-plugin", - "version": "1.0.0", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "obsidian-sample-plugin", - "version": "1.0.0", + "version": "1.1.0", "license": "MIT", "dependencies": { "express": "^4.21.2" diff --git a/settings.ts b/settings.ts index 001c89a..b5cd67b 100644 --- a/settings.ts +++ b/settings.ts @@ -131,26 +131,11 @@ export class TickTickSettingTab extends PluginSettingTab { new Setting(containerEl) .setName('Connect to TickTick') - .setDesc('Click to open the OAuth authorization URL in your browser.') + .setDesc('Opens the OAuth authorization page in your browser. After approving, you will be returned to Obsidian automatically.') .addButton(async (button) => { button.setButtonText('Connect').onClick(async () => { await this.plugin.startAuthFlow(); }); }); - - new Setting(containerEl) - .setName('Authorization code') - .setDesc('Paste the authorization code from TickTick here to obtain an access token.') - .addText(text => - text - .setPlaceholder('Enter authorization code') - .onChange(async (code) => { - if (code.trim()) { - await this.plugin.exchangeAuthCodeForToken(code.trim()); - text.setValue(''); - this.display(); - } - }) - ); } } \ No newline at end of file