mirror of
https://github.com/dukex/obsidian-import-todoist.git
synced 2026-07-22 05:31:46 +00:00
initial commit
This commit is contained in:
commit
f0ff95cd9b
8 changed files with 2800 additions and 0 deletions
34
.github/workflows/release.yaml
vendored
Normal file
34
.github/workflows/release.yaml
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
name: Release Obsidian plugin
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "18.x"
|
||||
|
||||
- name: Build plugin
|
||||
run: |
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
- name: Create release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
tag="${GITHUB_REF#refs/tags/}"
|
||||
|
||||
gh release create "$tag" \
|
||||
--title="$tag" \
|
||||
--draft \
|
||||
main.js manifest.json styles.css
|
||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
main.js
|
||||
node_modules
|
||||
data.json
|
||||
49
esbuild.config.mjs
Normal file
49
esbuild.config.mjs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import esbuild from "esbuild";
|
||||
import process from "process";
|
||||
import builtins from "builtin-modules";
|
||||
|
||||
const banner = `/*
|
||||
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
||||
if you want to view the source, please visit the github repository of this plugin
|
||||
*/
|
||||
`;
|
||||
|
||||
const prod = process.argv[2] === "production";
|
||||
|
||||
const context = await esbuild.context({
|
||||
banner: {
|
||||
js: banner,
|
||||
},
|
||||
entryPoints: ["main.ts"],
|
||||
bundle: true,
|
||||
external: [
|
||||
"obsidian",
|
||||
"electron",
|
||||
"@codemirror/autocomplete",
|
||||
"@codemirror/collab",
|
||||
"@codemirror/commands",
|
||||
"@codemirror/language",
|
||||
"@codemirror/lint",
|
||||
"@codemirror/search",
|
||||
"@codemirror/state",
|
||||
"@codemirror/view",
|
||||
"@lezer/common",
|
||||
"@lezer/highlight",
|
||||
"@lezer/lr",
|
||||
...builtins,
|
||||
],
|
||||
format: "cjs",
|
||||
target: "es2018",
|
||||
logLevel: "info",
|
||||
sourcemap: prod ? false : "inline",
|
||||
treeShaking: true,
|
||||
outfile: "main.js",
|
||||
minify: prod,
|
||||
});
|
||||
|
||||
if (prod) {
|
||||
await context.rebuild();
|
||||
process.exit(0);
|
||||
} else {
|
||||
await context.watch();
|
||||
}
|
||||
261
main.ts
Normal file
261
main.ts
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
import {
|
||||
App,
|
||||
Plugin,
|
||||
PluginSettingTab,
|
||||
Setting,
|
||||
requestUrl,
|
||||
type RequestUrlParam,
|
||||
Editor,
|
||||
} from "obsidian";
|
||||
|
||||
import moment from "moment";
|
||||
|
||||
interface ImportTodoistSettings {
|
||||
todoistApiKey: string;
|
||||
}
|
||||
|
||||
const IMPORT_TODOIST_SETTINGS: ImportTodoistSettings = {
|
||||
todoistApiKey: "apiKey",
|
||||
};
|
||||
|
||||
const makeToDoistRequest = async (
|
||||
url: string,
|
||||
method: string,
|
||||
apiKey: string,
|
||||
body?: string,
|
||||
) => {
|
||||
const requestParams: RequestUrlParam = {
|
||||
url: url,
|
||||
method: method,
|
||||
body: body,
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
};
|
||||
|
||||
if (method === "GET") {
|
||||
return requestUrl(requestParams)
|
||||
.then((res) => res.json)
|
||||
.then((res) => {
|
||||
return res;
|
||||
});
|
||||
} else {
|
||||
requestUrl(requestParams);
|
||||
}
|
||||
};
|
||||
|
||||
interface Task {
|
||||
creator_id: string;
|
||||
created_at: string;
|
||||
assignee_id: string;
|
||||
assigner_id: string;
|
||||
is_completed: boolean;
|
||||
content: string;
|
||||
description: string;
|
||||
due: {
|
||||
date: string;
|
||||
is_recurring: boolean;
|
||||
datetime: string;
|
||||
string: string;
|
||||
timezone: string;
|
||||
};
|
||||
duration: any;
|
||||
id: string;
|
||||
labels: string[];
|
||||
priority: number;
|
||||
project_id: string;
|
||||
section_id: string;
|
||||
parent_id: string;
|
||||
comments: Comment[];
|
||||
}
|
||||
|
||||
interface Comment {
|
||||
content: string;
|
||||
id: string;
|
||||
posted_at: string;
|
||||
task_id: string;
|
||||
}
|
||||
|
||||
interface Project {
|
||||
id: string;
|
||||
name: string;
|
||||
comment_count: number;
|
||||
order: number;
|
||||
color: string;
|
||||
is_shared: boolean;
|
||||
is_favorite: boolean;
|
||||
parent_id: string;
|
||||
is_inbox_project: boolean;
|
||||
is_team_inbox: boolean;
|
||||
view_style: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
class ImportAllTasksCommand {
|
||||
todoistApiKey: string;
|
||||
projects: Record<string, Project> = {};
|
||||
|
||||
constructor(todoistApiKey: string) {
|
||||
this.todoistApiKey = todoistApiKey;
|
||||
}
|
||||
|
||||
async execute(editor: Editor) {
|
||||
return this.loadData().then((tasks) => {
|
||||
console.log("Importing all tasks");
|
||||
|
||||
const markdownTasks = tasks.map((task: Task) => {
|
||||
return this.transformToMarkdown(task);
|
||||
});
|
||||
|
||||
editor.replaceRange(markdownTasks.join("\n---\n"), editor.getCursor());
|
||||
});
|
||||
}
|
||||
|
||||
private loadData() {
|
||||
return this.loadProjects()
|
||||
.then(() => {
|
||||
return this.allTasks();
|
||||
})
|
||||
.then((tasks) => {
|
||||
const enrichTasksWithComments = tasks.map((task: Task) => {
|
||||
return this.withComments(task);
|
||||
});
|
||||
|
||||
return Promise.all(enrichTasksWithComments);
|
||||
});
|
||||
}
|
||||
|
||||
private transformToMarkdown(task: Task) {
|
||||
const tags = task.labels
|
||||
.map((label: string) => {
|
||||
return `#${label}`;
|
||||
})
|
||||
.join(" ");
|
||||
|
||||
const created = `[created:: ${moment(task.created_at).format("YYYY-MM-DD")}]`;
|
||||
const status = task.is_completed ? "x" : " ";
|
||||
const description = task.description ? `\n${task.description}\n` : "";
|
||||
const comments = task.comments
|
||||
.map((comment) => {
|
||||
return `${comment.content}`;
|
||||
})
|
||||
.join("\n\n");
|
||||
|
||||
const due = task.due
|
||||
? `[due:: ${moment(task.due.datetime).format("YYYY-MM-DD")}]`
|
||||
: "";
|
||||
const id = `[id:: ${task.id}]`;
|
||||
const priority = `[priority:: ${["none", "low", "medium", "high", "highest"][task.priority]}]`;
|
||||
const project = `#project-${this.projects[task.project_id].name.replace(" ", "-")}`;
|
||||
|
||||
return `- [${status}] ${task.content} ${tags} ${project} ${created} ${priority} ${id} ${due} ${description}\n${comments}\n`;
|
||||
}
|
||||
|
||||
private loadProjects() {
|
||||
return makeToDoistRequest(
|
||||
`https://api.todoist.com/rest/v2/projects`,
|
||||
"GET",
|
||||
this.todoistApiKey,
|
||||
).then((projects: Project[]) => {
|
||||
projects.forEach((project) => {
|
||||
this.projects[project.id] = project;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private allTasks(): Promise<Task[]> {
|
||||
return makeToDoistRequest(
|
||||
"https://api.todoist.com/rest/v2/tasks",
|
||||
"GET",
|
||||
this.todoistApiKey,
|
||||
);
|
||||
}
|
||||
|
||||
private withComments(task: Task): Promise<Task> {
|
||||
return makeToDoistRequest(
|
||||
`https://api.todoist.com/rest/v2/comments?task_id=${task.id}`,
|
||||
"GET",
|
||||
this.todoistApiKey,
|
||||
).then((comments) => {
|
||||
task.comments = comments;
|
||||
return task;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class Commander {
|
||||
settings: ImportTodoistSettings;
|
||||
|
||||
constructor(settings: ImportTodoistSettings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
async importAllTasks(editor: Editor) {
|
||||
const command = new ImportAllTasksCommand(this.settings.todoistApiKey);
|
||||
return command.execute(editor);
|
||||
}
|
||||
}
|
||||
|
||||
export default class ImportTodoistPlugin extends Plugin {
|
||||
settings: ImportTodoistSettings;
|
||||
commander: Commander;
|
||||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
|
||||
this.commander = new Commander(this.settings);
|
||||
|
||||
this.addCommand({
|
||||
id: "import-todoist-all",
|
||||
name: "Import all tasks",
|
||||
editorCallback: (editor: Editor) => {
|
||||
return this.commander.importAllTasks(editor);
|
||||
},
|
||||
});
|
||||
|
||||
this.addSettingTab(new ImportTodoistSettingsTab(this.app, this));
|
||||
}
|
||||
|
||||
onunload() {}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign(
|
||||
{},
|
||||
IMPORT_TODOIST_SETTINGS,
|
||||
await this.loadData(),
|
||||
);
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
}
|
||||
|
||||
class ImportTodoistSettingsTab extends PluginSettingTab {
|
||||
plugin: ImportTodoistPlugin;
|
||||
|
||||
constructor(app: App, plugin: ImportTodoistPlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
display(): void {
|
||||
const { containerEl } = this;
|
||||
|
||||
containerEl.empty();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Todoist API Key")
|
||||
.setDesc("")
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("Enter your secret")
|
||||
.setValue(this.plugin.settings.todoistApiKey)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.todoistApiKey = value;
|
||||
await this.plugin.saveSettings();
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
11
manifest.json
Normal file
11
manifest.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"id": "import-todoist",
|
||||
"name": "Import Todoist",
|
||||
"version": "1.0.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Import Todoist tasks to Obsidian",
|
||||
"author": "Duke",
|
||||
"authorUrl": "https://empersonalmeidax.wtf",
|
||||
"fundingUrl": "https://github.com/sponsors/dukex",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
2398
package-lock.json
generated
Normal file
2398
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
27
package.json
Normal file
27
package.json
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"name": "import-todoist",
|
||||
"version": "1.0.0",
|
||||
"description": "This import todoist tasks to obsidian",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
||||
"version": "node version-bump.mjs && git add manifest.json versions.json"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||
"@typescript-eslint/parser": "5.29.0",
|
||||
"builtin-modules": "3.3.0",
|
||||
"esbuild": "0.17.3",
|
||||
"obsidian": "latest",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "4.7.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"moment": "^2.30.1"
|
||||
}
|
||||
}
|
||||
17
tsconfig.json
Normal file
17
tsconfig.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"inlineSourceMap": true,
|
||||
"inlineSources": true,
|
||||
"module": "ESNext",
|
||||
"target": "ES6",
|
||||
"allowJs": true,
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"isolatedModules": true,
|
||||
"strictNullChecks": true,
|
||||
"lib": ["DOM", "ES5", "ES6", "ES7"]
|
||||
},
|
||||
"include": ["**/*.ts"]
|
||||
}
|
||||
Loading…
Reference in a new issue