Skip to content

Commit e4467fa

Browse files
committed
feat: add task creation via /tt
1 parent 0c556b4 commit e4467fa

File tree

4 files changed

+151
-87
lines changed

4 files changed

+151
-87
lines changed

src/App.tsx

Lines changed: 0 additions & 26 deletions
This file was deleted.

src/main.tsx

Lines changed: 128 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,145 @@
1-
import "@logseq/libs";
1+
import '@logseq/libs';
2+
import moment from 'moment';
3+
import TickTick from './ticktick/ticktick';
4+
import { NewTask, Subtask } from './ticktick/task';
5+
import { logseq as PackageLogseq } from '../package.json';
6+
import { settingsSchema, getTickTickSettings } from './settings';
7+
import { BlockEntity } from '@logseq/libs/dist/LSPlugin';
28

3-
import React from "react";
4-
import * as ReactDOM from "react-dom/client";
5-
import App from "./App";
6-
import "./index.css";
9+
const pluginId = PackageLogseq.id;
10+
const ticktick = new TickTick();
711

8-
import { logseq as PL } from "../package.json";
12+
const priorityToNum = (text: string): 0 | 1 | 3 | 5 => {
13+
const priority = text.match(/\[#([A-C])\]/);
14+
if (priority) {
15+
switch (priority[1]) {
16+
case 'A':
17+
return 5;
18+
case 'B':
19+
return 3;
20+
case 'C':
21+
return 1;
22+
}
23+
}
24+
return 0;
25+
};
26+
27+
const priorityToTag = (priority: 0 | 1 | 3 | 5 | undefined): string => {
28+
switch (priority) {
29+
case 5:
30+
return ' [#A] ';
31+
case 3:
32+
return ' [#B] ';
33+
case 1:
34+
return ' [#C] ';
35+
}
36+
return '';
37+
};
938

10-
// @ts-expect-error
11-
const css = (t, ...args) => String.raw(t, ...args);
39+
const parseTask = (text: string): NewTask => {
40+
// parse priority and remove it from the title
41+
const priority = priorityToNum(text);
42+
let title = text.replace(/\[#([A-C])\]/, '');
43+
title = title.replace(/TODO/, '');
1244

13-
const pluginId = PL.id;
45+
return {
46+
title,
47+
priority,
48+
items: [],
49+
};
50+
};
1451

15-
function main() {
16-
console.info(`#${pluginId}: MAIN`);
17-
const root = ReactDOM.createRoot(document.getElementById("app")!);
18-
19-
root.render(
20-
<React.StrictMode>
21-
<App />
22-
</React.StrictMode>
23-
);
24-
25-
function createModel() {
26-
return {
27-
show() {
28-
logseq.showMainUI();
29-
},
30-
};
52+
const getTreeContent: (block: BlockEntity) => Promise<BlockEntity | null> = async (block) => {
53+
const blockEntity = await logseq.Editor.getBlock(block.uuid, { includeChildren: true });
54+
55+
return blockEntity;
56+
}
57+
58+
const flattenTree: (node: BlockEntity) => BlockEntity[] = (node) => {
59+
const result: BlockEntity[] = [node];
60+
61+
if (node.children) {
62+
for (const child of node.children) {
63+
result.push(...flattenTree(child as BlockEntity));
64+
}
3165
}
3266

33-
logseq.provideModel(createModel());
34-
logseq.setMainUIInlineStyle({
35-
zIndex: 11,
67+
return result;
68+
}
69+
70+
const main: () => Promise<void> = async () => {
71+
console.info(`#${pluginId}: MAIN`);
72+
73+
logseq.useSettingsSchema(settingsSchema);
74+
let settings = getTickTickSettings();
75+
76+
logseq.onSettingsChanged(() => {
77+
settings = getTickTickSettings();
78+
79+
if (
80+
settings.accessToken === '' &&
81+
settings.accessCode !== '' &&
82+
settings.clientId !== '' &&
83+
settings.clientSecret !== '' &&
84+
settings.redirectUri !== ''
85+
) {
86+
ticktick
87+
.getAccessToken(
88+
settings.clientId,
89+
settings.clientSecret,
90+
settings.accessCode,
91+
settings.redirectUri,
92+
)
93+
.then((accessToken) => {
94+
logseq.updateSettings({ accessToken });
95+
logseq.UI.showMsg(
96+
'TickTick access token updated successfully.',
97+
'success',
98+
);
99+
});
100+
}
36101
});
37102

38-
const openIconName = "template-plugin-open";
103+
if (settings.accessToken !== '') {
104+
ticktick.setAccessToken(settings.accessToken);
105+
}
39106

40-
logseq.provideStyle(css`
41-
.${openIconName} {
42-
opacity: 0.55;
43-
font-size: 20px;
44-
margin-top: 4px;
107+
logseq.Editor.registerSlashCommand('tt', async () => {
108+
const blockEntity = await logseq.Editor.getCurrentBlock();
109+
if (!blockEntity) {
110+
console.error('No block selected');
111+
return;
45112
}
46113

47-
.${openIconName}:hover {
48-
opacity: 0.9;
114+
let contentTree = await getTreeContent(blockEntity);
115+
if (!contentTree) {
116+
console.error('Cannot get tree content from block entity');
117+
return;
49118
}
50-
`);
51119

52-
logseq.App.registerUIItem("toolbar", {
53-
key: openIconName,
54-
template: `
55-
<div data-on-click="show" class="${openIconName}">⚙️</div>
56-
`,
120+
const flatContentTree = flattenTree(contentTree);
121+
console.log(flatContentTree);
122+
123+
const subtasks: Subtask[] = flatContentTree.slice(1).map(child => {
124+
const subtask: Subtask = {
125+
title: child.content.replace(/TODO/, '').replace(/\[#([A-C])\]/, ''),
126+
};
127+
return subtask;
128+
});
129+
130+
const task = parseTask(flatContentTree[0]?.content || '');
131+
task.items = subtasks;
132+
133+
try {
134+
const newTask = await ticktick.createTask(task);
135+
await logseq.Editor.updateBlock(
136+
blockEntity.uuid,
137+
`TODO${priorityToTag(newTask.priority)}[${newTask.title}](${newTask.taskUrl})`,
138+
);
139+
} catch (error) {
140+
logseq.UI.showMsg('TickTick access token is invalid.', 'error');
141+
}
57142
});
58143
}
59144

60-
logseq.ready(main).catch(console.error);
145+
logseq.ready(main).catch(console.error);

src/settings.ts

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,46 +2,47 @@ import { SettingSchemaDesc } from '@logseq/libs/dist/LSPlugin';
22

33
export const settingsSchema: SettingSchemaDesc[] = [
44
{
5-
key: 'ticktick.client_id',
5+
key: 'client_id',
66
type: 'string',
7-
title: 'Client ID',
7+
title: 'TickTick Client ID',
88
description: 'TickTick Client ID',
99
default: '',
1010
},
1111
{
12-
key: 'ticktick.client_secret',
12+
key: 'client_secret',
1313
type: 'string',
14-
title: 'Client Secret',
14+
title: 'TickTick Client Secret',
1515
description: 'TickTick Client Secret',
1616
default: '',
1717
},
1818
{
19-
key: 'ticktick.redirect_uri',
19+
key: 'redirect_uri',
2020
type: 'string',
21-
title: 'Redirect URI',
21+
title: 'TickTick Redirect URI',
2222
description: 'TickTick Redirect URI',
2323
default: '',
2424
},
2525
{
26-
key: 'ticktick.code',
26+
key: 'access_code',
2727
type: 'string',
28-
title: 'Code',
28+
title: 'TickTick Access Code',
2929
description: 'TickTick Code',
3030
default: 'https://mxschll.github.io/logseq-ticktick-plugin',
3131
},
3232
];
3333

3434
export const getTickTickSettings = () => {
35-
const clientId = logseq.settings!['ticktick.client_id'];
36-
const clientSecret = logseq.settings!['ticktick.client_secret'];
37-
const redirectUri = logseq.settings!['ticktick.redirect_uri'];
38-
const code = logseq.settings!['ticktick.code'];
39-
const accessToken = logseq.settings!['ticktick.access_token'] || '';
35+
const clientId = logseq.settings!['client_id'];
36+
const clientSecret = logseq.settings!['client_secret'];
37+
const redirectUri = logseq.settings!['redirect_uri'];
38+
const accessCode = logseq.settings!['access_code'];
39+
const accessToken = logseq.settings!['access_token'];
40+
4041
return {
41-
clientId,
42-
clientSecret,
43-
redirectUri,
44-
code,
45-
accessToken,
42+
clientId: clientId || '',
43+
clientSecret: clientSecret || '',
44+
redirectUri: redirectUri || '',
45+
accessCode: accessCode || '',
46+
accessToken: accessToken || '',
4647
};
4748
};

src/ticktick/ticktick.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ class TickTick {
3333
return data.access_token;
3434
}
3535

36+
public setAccessToken(accessToken: string): void {
37+
this.accessToken = accessToken;
38+
}
39+
3640
private generateTaskUrl(task: Task): string {
3741
return `${this.WEB_URL}/#p/${encodeURIComponent(task.projectId)}/task/${encodeURIComponent(task.id)}`;
3842
}

0 commit comments

Comments
 (0)