From 0f98a565529f002ae46e981a1f9b9dd1d748eb97 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 4 Jun 2026 20:30:07 +0000 Subject: [PATCH] Release 1.2.1: address Obsidian directory review warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - manifest: drop "Obsidian" from description (directory rule), remove unknown `main` field, bump to 1.2.1 - README: title now matches plugin name in manifest - Remove unused base64UrlEncode helper - Remove dead express dependency and server.js (clears qs / path-to-regexp vulnerability warnings) - package.json: real name + author + description, drop sample-plugin template metadata - versions.json: add 1.2.1 → 1.4.0 https://claude.ai/code/session_01V2fsiatACzjUCNwSEhwER9 --- README.md | 2 +- main.js | 8 +- main.ts | 11 -- manifest.json | 9 +- package-lock.json | 419 +--------------------------------------------- package.json | 11 +- server.js | 15 -- versions.json | 3 +- 8 files changed, 19 insertions(+), 459 deletions(-) delete mode 100644 server.js diff --git a/README.md b/README.md index 914a8fe..6730f84 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# TickTick Quick Add for Obsidian +# TickTick Quick Add Task Send the line or paragraph at your cursor straight to TickTick as a new task — with a one-tap link back to the exact spot in your note. Works on desktop and on mobile (iOS and Android). diff --git a/main.js b/main.js index 27128d2..592061c 100644 --- a/main.js +++ b/main.js @@ -3,8 +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 T=Object.defineProperty;var E=Object.getOwnPropertyDescriptor;var A=Object.getOwnPropertyNames;var I=Object.prototype.hasOwnProperty;var U=(a,e)=>{for(var n in e)T(a,n,{get:e[n],enumerable:!0})},_=(a,e,n,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let t of A(e))!I.call(a,t)&&t!==n&&T(a,t,{get:()=>e[t],enumerable:!(i=E(e,t))||i.enumerable});return a};var N=a=>_(T({},"__esModule",{value:!0}),a);var O={};U(O,{default:()=>k});module.exports=N(O);var S=require("obsidian"),o=require("obsidian");var c=require("obsidian"),f={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,n){super(e,n),this.plugin=n}display(){let{containerEl:e}=this;e.empty(),this.plugin.settings.accessToken?this.renderConnectedView(e):this.renderSetupView(e)}renderSetupView(e){e.createEl("h2",{text:"Set up TickTick"}),e.createEl("p",{text:"Connect this plugin to your TickTick account in two steps. You only need to do this once."}),new c.Setting(e).setName("Step 1 \u2014 Enter your TickTick app credentials").setHeading();let n=e.createEl("p");n.appendText("Sign in to the ");let i=n.createEl("a",{text:"TickTick Developer Portal",href:"https://developer.ticktick.com/"});i.setAttr("target","_blank"),i.setAttr("rel","noopener"),n.appendText(", create an app, and copy its Client ID and Client Secret into the fields below. In the portal, set the app's Redirect URI to the value shown in the Redirect URI field."),new c.Setting(e).setName("Client ID").addText(t=>t.setPlaceholder("Your Client ID").setValue(this.plugin.settings.clientId||"").onChange(async s=>{this.plugin.settings.clientId=s.trim(),await this.plugin.saveSettings()})),new c.Setting(e).setName("Client Secret").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 s=>{this.plugin.settings.clientSecret=s.trim(),await this.plugin.saveSettings()}),t)),new c.Setting(e).setName("Redirect URI").setDesc("Copy this exact value into your TickTick app's Redirect URI field in the developer portal. Only change it here if you self-host the callback page.").addText(t=>t.setPlaceholder(f.redirectUri).setValue(this.plugin.settings.redirectUri||"").onChange(async s=>{this.plugin.settings.redirectUri=s.trim(),await this.plugin.saveSettings()})),new c.Setting(e).setName("Step 2 \u2014 Authorize").setHeading(),e.createEl("p",{text:"Tap Connect, then tap the link in the popup to sign in to TickTick and approve access. The callback page will either return you to Obsidian automatically, or show you an authorization code \u2014 if you see a code, copy it and paste it in the field below."}),new c.Setting(e).setName("Connect to TickTick").addButton(t=>{t.setButtonText("Connect").setCta().onClick(async()=>{await this.plugin.startAuthFlow()})}),new c.Setting(e).setName("Authorization code").setDesc("Only needed if the callback page showed you a code instead of returning you to Obsidian automatically.").addText(t=>t.setPlaceholder("Paste authorization code").onChange(async s=>{s.trim()&&(await this.plugin.exchangeAuthCodeForToken(s.trim()),t.setValue(""),this.display())}))}renderConnectedView(e){e.createEl("h2",{text:"Connected to TickTick"}),e.createEl("p",{text:'Use the "Create TickTick task" command from any note to send the current line or paragraph to TickTick.'}),new c.Setting(e).setName("Plugin behavior").setHeading(),new c.Setting(e).setName("Selection mode").setDesc("Capture 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 s=>{this.plugin.settings.selectionMode=s,await this.plugin.saveSettings()})),new c.Setting(e).setName("Tag position").setDesc("Where the #ticktick tag is added relative to the text.").addDropdown(t=>t.addOption("append","Append (end)").addOption("prepend","Prepend (beginning)").setValue(this.plugin.settings.tagPosition).onChange(async s=>{this.plugin.settings.tagPosition=s,await this.plugin.saveSettings()}));let n=e.createEl("details",{cls:"ticktick-advanced"});n.style.marginTop="2em";let i=n.createEl("summary",{text:"Advanced \u2014 Connection details"});i.style.cursor="pointer",i.style.fontWeight="600",i.style.padding="0.5em 0",new c.Setting(n).setName("Reconnect").setDesc("Re-run the OAuth flow. Use if your token can no longer be refreshed.").addButton(t=>{t.setButtonText("Reconnect").onClick(async()=>{await this.plugin.startAuthFlow()})}),new c.Setting(n).setName("Authorization code").setDesc("Manual fallback if reconnecting did not return you to Obsidian automatically.").addText(t=>t.setPlaceholder("Paste authorization code").onChange(async s=>{s.trim()&&(await this.plugin.exchangeAuthCodeForToken(s.trim()),t.setValue(""),this.display())})),new c.Setting(n).setName("Disconnect").setDesc("Clear stored tokens. You will need to authorize again to create tasks.").addButton(t=>{t.setButtonText("Disconnect").setWarning().onClick(async()=>{this.plugin.settings.accessToken="",this.plugin.settings.refreshToken=void 0,this.plugin.settings.tokenExpiry=void 0,await this.plugin.saveSettings(),new c.Notice("Disconnected from TickTick."),this.display()})}),new c.Setting(n).setName("Client ID").addText(t=>t.setValue(this.plugin.settings.clientId||"").onChange(async s=>{this.plugin.settings.clientId=s.trim(),await this.plugin.saveSettings()})),new c.Setting(n).setName("Client Secret").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 s=>{this.plugin.settings.clientSecret=s.trim(),await this.plugin.saveSettings()}),t)),new c.Setting(n).setName("Redirect URI").addText(t=>t.setValue(this.plugin.settings.redirectUri||"").onChange(async s=>{this.plugin.settings.redirectUri=s.trim(),await this.plugin.saveSettings()})),new c.Setting(n).setName("Access token").setDesc("Stored access token (read-only).").addText(t=>{t.inputEl.readOnly=!0,t.setValue(this.plugin.settings.accessToken||"")}),new c.Setting(n).setName("Refresh token").setDesc("Stored refresh token (read-only).").addText(t=>{t.inputEl.readOnly=!0,t.setValue(this.plugin.settings.refreshToken||"")})}};function w(a){let e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",n="";for(let i=0;i0&&a.getLine(n-1).trim()!=="";)n--;for(;i{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",icon:"lucide-circle-check-big",editorCallback:(e,n)=>this.createTaskFromEditor(e,n)}),this.registerEvent(this.app.workspace.on("file-menu",(e,n)=>{if(!(n instanceof o.TFile)||n.extension!=="md")return;let i=this.app.workspace.getActiveViewOfType(o.MarkdownView);!i||!i.editor||i.file!==n||e.addItem(t=>{t.setTitle("Create TickTick task").setIcon("lucide-circle-check-big").onClick(async()=>{await this.createTaskFromEditor(i.editor,i)})})}))}async createTaskFromEditor(e,n){let i=e.getCursor();if(!n.file){new o.Notice("No file found for the current view!");return}let t,s,r;if(this.settings.selectionMode==="paragraph"){let{text:h,start:g,end:P}=R(e,i.line);t=h,s=g,r=P}else{let{text:h,line:g}=D(e,i.line);t=h,s=g,r=g}if(!t.trim()){new o.Notice("No text found on the current line!");return}let d=w(8),l=this.settings.tagPosition==="prepend"?`#ticktick ${t} ^${d}`:`${t} #ticktick ^${d}`;e.replaceRange(l,{line:s,ch:0},{line:r,ch:e.getLine(r).length});let p=this.app.vault.getName(),x=n.file.path,C=`obsidian://advanced-uri?vault=${encodeURIComponent(p)}&filepath=${encodeURIComponent(x)}&block=${encodeURIComponent(d)}`,b=`${t} +var T=Object.defineProperty;var E=Object.getOwnPropertyDescriptor;var I=Object.getOwnPropertyNames;var A=Object.prototype.hasOwnProperty;var U=(a,e)=>{for(var n in e)T(a,n,{get:e[n],enumerable:!0})},_=(a,e,n,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let t of I(e))!A.call(a,t)&&t!==n&&T(a,t,{get:()=>e[t],enumerable:!(i=E(e,t))||i.enumerable});return a};var N=a=>_(T({},"__esModule",{value:!0}),a);var O={};U(O,{default:()=>k});module.exports=N(O);var x=require("obsidian"),o=require("obsidian");var c=require("obsidian"),m={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,n){super(e,n),this.plugin=n}display(){let{containerEl:e}=this;e.empty(),this.plugin.settings.accessToken?this.renderConnectedView(e):this.renderSetupView(e)}renderSetupView(e){e.createEl("h2",{text:"Set up TickTick"}),e.createEl("p",{text:"Connect this plugin to your TickTick account in two steps. You only need to do this once."}),new c.Setting(e).setName("Step 1 \u2014 Enter your TickTick app credentials").setHeading();let n=e.createEl("p");n.appendText("Sign in to the ");let i=n.createEl("a",{text:"TickTick Developer Portal",href:"https://developer.ticktick.com/"});i.setAttr("target","_blank"),i.setAttr("rel","noopener"),n.appendText(", create an app, and copy its Client ID and Client Secret into the fields below. In the portal, set the app's Redirect URI to the value shown in the Redirect URI field."),new c.Setting(e).setName("Client ID").addText(t=>t.setPlaceholder("Your Client ID").setValue(this.plugin.settings.clientId||"").onChange(async s=>{this.plugin.settings.clientId=s.trim(),await this.plugin.saveSettings()})),new c.Setting(e).setName("Client Secret").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 s=>{this.plugin.settings.clientSecret=s.trim(),await this.plugin.saveSettings()}),t)),new c.Setting(e).setName("Redirect URI").setDesc("Copy this exact value into your TickTick app's Redirect URI field in the developer portal. Only change it here if you self-host the callback page.").addText(t=>t.setPlaceholder(m.redirectUri).setValue(this.plugin.settings.redirectUri||"").onChange(async s=>{this.plugin.settings.redirectUri=s.trim(),await this.plugin.saveSettings()})),new c.Setting(e).setName("Step 2 \u2014 Authorize").setHeading(),e.createEl("p",{text:"Tap Connect, then tap the link in the popup to sign in to TickTick and approve access. The callback page will either return you to Obsidian automatically, or show you an authorization code \u2014 if you see a code, copy it and paste it in the field below."}),new c.Setting(e).setName("Connect to TickTick").addButton(t=>{t.setButtonText("Connect").setCta().onClick(async()=>{await this.plugin.startAuthFlow()})}),new c.Setting(e).setName("Authorization code").setDesc("Only needed if the callback page showed you a code instead of returning you to Obsidian automatically.").addText(t=>t.setPlaceholder("Paste authorization code").onChange(async s=>{s.trim()&&(await this.plugin.exchangeAuthCodeForToken(s.trim()),t.setValue(""),this.display())}))}renderConnectedView(e){e.createEl("h2",{text:"Connected to TickTick"}),e.createEl("p",{text:'Use the "Create TickTick task" command from any note to send the current line or paragraph to TickTick.'}),new c.Setting(e).setName("Plugin behavior").setHeading(),new c.Setting(e).setName("Selection mode").setDesc("Capture 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 s=>{this.plugin.settings.selectionMode=s,await this.plugin.saveSettings()})),new c.Setting(e).setName("Tag position").setDesc("Where the #ticktick tag is added relative to the text.").addDropdown(t=>t.addOption("append","Append (end)").addOption("prepend","Prepend (beginning)").setValue(this.plugin.settings.tagPosition).onChange(async s=>{this.plugin.settings.tagPosition=s,await this.plugin.saveSettings()}));let n=e.createEl("details",{cls:"ticktick-advanced"});n.style.marginTop="2em";let i=n.createEl("summary",{text:"Advanced \u2014 Connection details"});i.style.cursor="pointer",i.style.fontWeight="600",i.style.padding="0.5em 0",new c.Setting(n).setName("Reconnect").setDesc("Re-run the OAuth flow. Use if your token can no longer be refreshed.").addButton(t=>{t.setButtonText("Reconnect").onClick(async()=>{await this.plugin.startAuthFlow()})}),new c.Setting(n).setName("Authorization code").setDesc("Manual fallback if reconnecting did not return you to Obsidian automatically.").addText(t=>t.setPlaceholder("Paste authorization code").onChange(async s=>{s.trim()&&(await this.plugin.exchangeAuthCodeForToken(s.trim()),t.setValue(""),this.display())})),new c.Setting(n).setName("Disconnect").setDesc("Clear stored tokens. You will need to authorize again to create tasks.").addButton(t=>{t.setButtonText("Disconnect").setWarning().onClick(async()=>{this.plugin.settings.accessToken="",this.plugin.settings.refreshToken=void 0,this.plugin.settings.tokenExpiry=void 0,await this.plugin.saveSettings(),new c.Notice("Disconnected from TickTick."),this.display()})}),new c.Setting(n).setName("Client ID").addText(t=>t.setValue(this.plugin.settings.clientId||"").onChange(async s=>{this.plugin.settings.clientId=s.trim(),await this.plugin.saveSettings()})),new c.Setting(n).setName("Client Secret").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 s=>{this.plugin.settings.clientSecret=s.trim(),await this.plugin.saveSettings()}),t)),new c.Setting(n).setName("Redirect URI").addText(t=>t.setValue(this.plugin.settings.redirectUri||"").onChange(async s=>{this.plugin.settings.redirectUri=s.trim(),await this.plugin.saveSettings()})),new c.Setting(n).setName("Access token").setDesc("Stored access token (read-only).").addText(t=>{t.inputEl.readOnly=!0,t.setValue(this.plugin.settings.accessToken||"")}),new c.Setting(n).setName("Refresh token").setDesc("Stored refresh token (read-only).").addText(t=>{t.inputEl.readOnly=!0,t.setValue(this.plugin.settings.refreshToken||"")})}};function w(a){let e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",n="";for(let i=0;i0&&a.getLine(n-1).trim()!=="";)n--;for(;i{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",icon:"lucide-circle-check-big",editorCallback:(e,n)=>this.createTaskFromEditor(e,n)}),this.registerEvent(this.app.workspace.on("file-menu",(e,n)=>{if(!(n instanceof o.TFile)||n.extension!=="md")return;let i=this.app.workspace.getActiveViewOfType(o.MarkdownView);!i||!i.editor||i.file!==n||e.addItem(t=>{t.setTitle("Create TickTick task").setIcon("lucide-circle-check-big").onClick(async()=>{await this.createTaskFromEditor(i.editor,i)})})}))}async createTaskFromEditor(e,n){let i=e.getCursor();if(!n.file){new o.Notice("No file found for the current view!");return}let t,s,r;if(this.settings.selectionMode==="paragraph"){let{text:h,start:g,end:P}=R(e,i.line);t=h,s=g,r=P}else{let{text:h,line:g}=D(e,i.line);t=h,s=g,r=g}if(!t.trim()){new o.Notice("No text found on the current line!");return}let d=w(8),l=this.settings.tagPosition==="prepend"?`#ticktick ${t} ^${d}`:`${t} #ticktick ^${d}`;e.replaceRange(l,{line:s,ch:0},{line:r,ch:e.getLine(r).length});let p=this.app.vault.getName(),S=n.file.path,C=`obsidian://advanced-uri?vault=${encodeURIComponent(p)}&filepath=${encodeURIComponent(S)}&block=${encodeURIComponent(d)}`,b=`${t} -[Open in Obsidian](${C})`,v=t.length>50?t.substring(0,50)+"...":t;await this.ensureFreshToken();try{(await this.createTicktickTask(v,b)).success?new o.Notice("TickTick task created successfully!"):new o.Notice("Failed to create TickTick task.")}catch(h){m(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 n=this.settings.redirectUri||"https://ticktick-quick-add-obsidian-6yawfmvnj-mooshs-projects-0635287d.vercel.app",i=encodeURIComponent("tasks:read tasks:write"),{codeVerifier:t,codeChallenge:s}=await V(),r=w(32);this.settings.tempCodeVerifier=t,this.settings.tempState=r,await this.saveSettings();let d=`https://ticktick.com/oauth/authorize?client_id=${e}&redirect_uri=${encodeURIComponent(n)}&response_type=code&scope=${i}&code_challenge=${s}&code_challenge_method=S256&state=${r}`;new y(this.app,d).open()}async exchangeAuthCodeForToken(e){let n="https://ticktick.com/oauth/token",i=this.settings.redirectUri||"https://ticktick-quick-add-obsidian-6yawfmvnj-mooshs-projects-0635287d.vercel.app",t=this.settings.clientId,s=this.settings.clientSecret,r=this.settings.tempCodeVerifier;if(!r||!t||!s){new o.Notice("Missing required credentials. Please update your settings.");return}let d=new URLSearchParams({grant_type:"authorization_code",code:e,redirect_uri:i,client_id:t,client_secret:s,code_verifier:r});try{let l=await(0,o.requestUrl)({url:n,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 p=l.json;p.refresh_token?this.settings.refreshToken=p.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=p.access_token,this.settings.tokenExpiry=Date.now()+p.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",n=this.settings.clientId,i=this.settings.clientSecret,t=new URLSearchParams({grant_type:"refresh_token",refresh_token:this.settings.refreshToken,client_id:n,client_secret:i});try{let s=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:t.toString()});if(s.status===200){let r=s.json;r.refresh_token&&(this.settings.refreshToken=r.refresh_token),this.settings.accessToken=r.access_token,this.settings.tokenExpiry=Date.now()+r.expires_in*1e3*.85,await this.saveSettings(),new o.Notice("Access token refreshed successfully!")}else throw console.error("Refresh token error:",s.text),new Error("Failed to refresh token")}catch(s){throw console.error("Error refreshing access token:",s),s}}async ensureFreshToken(){if(this.settings.tokenExpiry&&Date.now()>this.settings.tokenExpiry){m(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,n){let i=this.settings.accessToken;if(!i)return new o.Notice("No access token found. Please connect to TickTick in the settings."),{success:!1};let t="https://api.ticktick.com/open/v1/task",s={title:e,content:n};try{m(this,"task_creation_request",{endpoint:t,title:e});let r=await(0,o.requestUrl)({url:t,method:"POST",headers:{Authorization:`Bearer ${i}`,"Content-Type":"application/json","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64)"},body:JSON.stringify(s)});return r.status===200?{success:!0}:(console.error("TickTick API error (status):",r.status),console.log("response.json:",r.json),console.log("response.text:",r.text),{success:!1})}catch(r){return console.error("TickTick API error:",r),{success:!1}}}async loadSettings(){this.settings=Object.assign({},f,await this.loadData())}async saveSettings(){await this.saveData(this.settings)}}; +[Open in Obsidian](${C})`,v=t.length>50?t.substring(0,50)+"...":t;await this.ensureFreshToken();try{(await this.createTicktickTask(v,b)).success?new o.Notice("TickTick task created successfully!"):new o.Notice("Failed to create TickTick task.")}catch(h){f(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 n=this.settings.redirectUri||"https://ticktick-quick-add-obsidian-6yawfmvnj-mooshs-projects-0635287d.vercel.app",i=encodeURIComponent("tasks:read tasks:write"),{codeVerifier:t,codeChallenge:s}=await V(),r=w(32);this.settings.tempCodeVerifier=t,this.settings.tempState=r,await this.saveSettings();let d=`https://ticktick.com/oauth/authorize?client_id=${e}&redirect_uri=${encodeURIComponent(n)}&response_type=code&scope=${i}&code_challenge=${s}&code_challenge_method=S256&state=${r}`;new y(this.app,d).open()}async exchangeAuthCodeForToken(e){let n="https://ticktick.com/oauth/token",i=this.settings.redirectUri||"https://ticktick-quick-add-obsidian-6yawfmvnj-mooshs-projects-0635287d.vercel.app",t=this.settings.clientId,s=this.settings.clientSecret,r=this.settings.tempCodeVerifier;if(!r||!t||!s){new o.Notice("Missing required credentials. Please update your settings.");return}let d=new URLSearchParams({grant_type:"authorization_code",code:e,redirect_uri:i,client_id:t,client_secret:s,code_verifier:r});try{let l=await(0,o.requestUrl)({url:n,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 p=l.json;p.refresh_token?this.settings.refreshToken=p.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=p.access_token,this.settings.tokenExpiry=Date.now()+p.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",n=this.settings.clientId,i=this.settings.clientSecret,t=new URLSearchParams({grant_type:"refresh_token",refresh_token:this.settings.refreshToken,client_id:n,client_secret:i});try{let s=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:t.toString()});if(s.status===200){let r=s.json;r.refresh_token&&(this.settings.refreshToken=r.refresh_token),this.settings.accessToken=r.access_token,this.settings.tokenExpiry=Date.now()+r.expires_in*1e3*.85,await this.saveSettings(),new o.Notice("Access token refreshed successfully!")}else throw console.error("Refresh token error:",s.text),new Error("Failed to refresh token")}catch(s){throw console.error("Error refreshing access token:",s),s}}async ensureFreshToken(){if(this.settings.tokenExpiry&&Date.now()>this.settings.tokenExpiry){f(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,n){let i=this.settings.accessToken;if(!i)return new o.Notice("No access token found. Please connect to TickTick in the settings."),{success:!1};let t="https://api.ticktick.com/open/v1/task",s={title:e,content:n};try{f(this,"task_creation_request",{endpoint:t,title:e});let r=await(0,o.requestUrl)({url:t,method:"POST",headers:{Authorization:`Bearer ${i}`,"Content-Type":"application/json","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64)"},body:JSON.stringify(s)});return r.status===200?{success:!0}:(console.error("TickTick API error (status):",r.status),console.log("response.json:",r.json),console.log("response.text:",r.text),{success:!1})}catch(r){return console.error("TickTick API error:",r),{success:!1}}}async loadSettings(){this.settings=Object.assign({},m,await this.loadData())}async saveSettings(){await this.saveData(this.settings)}}; diff --git a/main.ts b/main.ts index 43c5042..4c0fc81 100644 --- a/main.ts +++ b/main.ts @@ -37,17 +37,6 @@ function getParagraph(editor: Editor, currentLine: number): { text: string, star return { text: paragraphText.trim(), start, end }; } -// Helper function to base64url-encode an ArrayBuffer -function base64UrlEncode(buffer: ArrayBuffer): string { - const bytes = new Uint8Array(buffer); - let binary = ''; - bytes.forEach(b => (binary += String.fromCharCode(b))); - return btoa(binary) - .replace(/\+/g, '-') - .replace(/\//g, '_') - .replace(/=+$/, ''); -} - // Helper function to generate PKCE codes: codeVerifier and corresponding codeChallenge async function generatePKCECodes(): Promise<{ codeVerifier: string; codeChallenge: string }> { const codeVerifier = generateRandomString(64); // between 43 and 128 characters diff --git a/manifest.json b/manifest.json index 210de1b..0cf0871 100644 --- a/manifest.json +++ b/manifest.json @@ -1,11 +1,10 @@ { "id": "ticktick-quickadd-task", "name": "TickTick Quick Add Task", - "version": "1.2.0", + "version": "1.2.1", "minAppVersion": "1.4.0", - "description": "Create TickTick tasks from text blocks with automatic Obsidian URI links. Requires Advanced URI plugin.", + "description": "Create TickTick tasks from text at your cursor with automatic deep links back to your notes. Requires the Advanced URI plugin.", "author": "Muxin Li", "authorUrl": "https://github.com/heymoosh", - "isDesktopOnly": false, - "main": "main.js" -} \ No newline at end of file + "isDesktopOnly": false +} diff --git a/package-lock.json b/package-lock.json index 4f4ff8c..06a764a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,13 @@ { - "name": "obsidian-sample-plugin", - "version": "1.1.0", + "name": "ticktick-quickadd-task", + "version": "1.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "obsidian-sample-plugin", - "version": "1.1.0", + "name": "ticktick-quickadd-task", + "version": "1.2.1", "license": "MIT", - "dependencies": { - "express": "^4.21.2" - }, "devDependencies": { "@types/node": "^16.11.6", "@typescript-eslint/eslint-plugin": "5.29.0", @@ -46,278 +43,6 @@ "w3c-keyname": "^2.2.4" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", - "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", - "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", - "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", - "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", - "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", - "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", - "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", - "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", - "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", - "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", - "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", - "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", - "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", - "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", - "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", - "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@esbuild/linux-x64": { "version": "0.25.4", "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", @@ -335,142 +60,6 @@ "node": ">=18" } }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", - "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", - "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", - "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", - "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", - "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", - "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", - "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", - "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz", diff --git a/package.json b/package.json index bceb5d7..cf2525e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "obsidian-sample-plugin", - "version": "1.2.0", - "description": "This is a sample plugin for Obsidian (https://obsidian.md)", + "name": "ticktick-quickadd-task", + "version": "1.2.1", + "description": "Create TickTick tasks from text at your cursor with automatic deep links back to your notes. Requires the Advanced URI plugin.", "main": "main.js", "scripts": { "dev": "node esbuild.config.mjs", @@ -9,7 +9,7 @@ "version": "node version-bump.mjs && git add manifest.json versions.json" }, "keywords": [], - "author": "", + "author": "Muxin Li", "license": "MIT", "devDependencies": { "@types/node": "^16.11.6", @@ -20,8 +20,5 @@ "obsidian": "latest", "tslib": "2.4.0", "typescript": "4.7.4" - }, - "dependencies": { - "express": "^4.21.2" } } diff --git a/server.js b/server.js deleted file mode 100644 index e2653f5..0000000 --- a/server.js +++ /dev/null @@ -1,15 +0,0 @@ -const express = require('express'); -const app = express(); -const port = 3000; - -app.get('/callback', (req, res) => { - const { code, state } = req.query; - // Here, you can just display the code and state - res.send(`Authorization code received: ${code}
State: ${state}

Copy this code into Obsidian!`); - console.log('Received code:', code); - console.log('Received state:', state); -}); - -app.listen(port, () => { - console.log(`Server listening at http://127.0.0.1:${port}`); -}); \ No newline at end of file diff --git a/versions.json b/versions.json index eed81ad..72e8e09 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,6 @@ { "1.0.0": "0.15.0", "1.1.0": "1.4.0", - "1.2.0": "1.4.0" + "1.2.0": "1.4.0", + "1.2.1": "1.4.0" }