├── .github
└── FUNDING.yml
├── .gitignore
├── Action Descriptions.md
├── Draftist Instructions.md
├── Draftist.js
├── LICENSE
├── README.md
├── docs
├── Draftist.js.html
├── global.html
└── index.html
├── drafts.d.ts
└── jsdoc-conf.json
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | custom: ['https://www.buymeacoffee.com/flohgro']
2 | patreon: flohgro
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | .DS_Store
3 | docs/fonts/
4 | docs/scripts/
5 | docs/styles/
6 | node_modules/
7 | package-lock.json
8 | package.json
9 |
--------------------------------------------------------------------------------
/Action Descriptions.md:
--------------------------------------------------------------------------------
1 | # Draftist Action Descriptions
2 |
3 |
4 |
5 | - [Draftist Action Descriptions](#draftist-action-descriptions)
6 | - [General Actions](#general-actions)
7 | - [Draftist Instructions](#draftist-instructions)
8 | - [Draftist Setup/Update](#draftist-setupupdate)
9 | - [Draftist](#draftist)
10 | - [Draftist Settings](#draftist-settings)
11 | - [Draftist Action Replicator](#draftist-action-replicator)
12 | - [Update local Todoist data](#update-local-todoist-data)
13 | - [Quick Add Tasks](#quick-add-tasks)
14 | - [quick add lines from draft](#quick-add-lines-from-draft)
15 | - [quick add lines from prompt](#quick-add-lines-from-prompt)
16 | - [Create Single Task](#create-single-task)
17 | - [task with description from draft](#task-with-description-from-draft)
18 | - [task with description from prompt](#task-with-description-from-prompt)
19 | - [task with description & settings from draft](#task-with-description--settings-from-draft)
20 | - [task with description & settings from prompt](#task-with-description--settings-from-prompt)
21 | - [Create (cross)Linked Task From Draft](#create-crosslinked-task-from-draft)
22 | - [create linked task in inbox](#create-linked-task-in-inbox)
23 | - [create linked task with settings](#create-linked-task-with-settings)
24 | - [create crosslinked task in inbox](#create-crosslinked-task-in-inbox)
25 | - [create crosslinked task with settings](#create-crosslinked-task-with-settings)
26 | - [Create Multiple tasks](#create-multiple-tasks)
27 | - [tasks with same settings from draft](#tasks-with-same-settings-from-draft)
28 | - [tasks with same settings from prompt](#tasks-with-same-settings-from-prompt)
29 | - [tasks with individual settings from draft](#tasks-with-individual-settings-from-draft)
30 | - [tasks with individual settings from prompt](#tasks-with-individual-settings-from-prompt)
31 | - [Create Tasks from MD Tasks in Draft](#create-tasks-from-md-tasks-in-draft)
32 | - [quick add tasks from md tasks in draft](#quick-add-tasks-from-md-tasks-in-draft)
33 | - [tasks with same settings from md tasks in draft](#tasks-with-same-settings-from-md-tasks-in-draft)
34 | - [tasks with individual settings from md tasks in draft](#tasks-with-individual-settings-from-md-tasks-in-draft)
35 | - [Import Tasks](#import-tasks)
36 | - [import todays tasks](#import-todays-tasks)
37 | - [import tasks from project](#import-tasks-from-project)
38 | - [import tasks from selected project](#import-tasks-from-selected-project)
39 | - [import tasks with all configured label(s)](#import-tasks-with-all-configured-labels)
40 | - [import tasks with any configured label(s)](#import-tasks-with-any-configured-labels)
41 | - [import tasks with all selected label(s)](#import-tasks-with-all-selected-labels)
42 | - [import tasks with any selected label(s)](#import-tasks-with-any-selected-labels)
43 | - [import tasks from filter](#import-tasks-from-filter)
44 | - [import tasks from filter in prompt](#import-tasks-from-filter-in-prompt)
45 | - [Modify Tasks](#modify-tasks)
46 | - [update labels of selected tasks from filter](#update-labels-of-selected-tasks-from-filter)
47 | - [duplicate selected tasks with other label](#duplicate-selected-tasks-with-other-label)
48 | - [update individual due date of selected task from filter](#update-individual-due-date-of-selected-task-from-filter)
49 | - [update due date of selected task from filter to same date](#update-due-date-of-selected-task-from-filter-to-same-date)
50 | - [resolve selected tasks from filter](#resolve-selected-tasks-from-filter)
51 | - [delete selected tasks from filter](#delete-selected-tasks-from-filter)
52 |
53 |
54 |
55 | Here you will find all descriptions of the Actions in Draftist.
56 | The description will also contain the default settings for the "after success" option in the Action.
57 |
58 | ## General Actions
59 |
60 | ### Draftist Instructions
61 |
62 | > This Action presents instructions on how to use the Draftist Action Group.
63 | >
64 | > After Success Setting: Nothing
65 |
66 | ### Draftist Setup/Update
67 |
68 | > This Action is used to setup or update the Draftist.js file in the iCloud directory of Draft at the path /Library/Scripts/. It downloads the latest version from the GitHub repository of Draftist at https://github.com/FlohGro-dev/Draftist.
69 | >
70 | > After Success Setting: Nothing
71 |
72 | ### Draftist
73 |
74 | > This Action loads all relevant functions that Draftist provides. Every Draftist Action includes this Action.
75 | If you want to make you're own Action based on Draftist functions simply include this Action at the beginning.
76 | >
77 | > After Success Setting: Nothing
78 |
79 | ### Draftist Settings
80 |
81 | > This Action allows changing the default settings of Draftist or restore the default settings based on the user selections.
82 | >
83 | > After Success Setting: Nothing
84 |
85 | ### Draftist Action Replicator
86 |
87 | > This Action eases the replication of an Action in the Draftist Action Group. When a user runs this Action it will display a prompt with all available Draftist Actions as buttons. When the user selects one action the installURL of this Action will be opened. This results in an *Action Import* prompt. Make sure to select `Import as New Action` in the prompt and select the destination Action Group of the replicated Action. After the Action is installed rename it to your personal choice.
88 | >
89 | > After Success Setting: Nothing
90 |
91 | ### Update local Todoist data
92 |
93 | > This Action forces an update of the locally stored Todoist data. Use this if you e.g. just created a new project / label that you want to use immediately. Updates will be performed after 24 hours by default - you can change this using the [Draftist Settings](#draftist-settings) action.
94 | >
95 | > After Success Setting: Nothing
96 |
97 | ## Quick Add Tasks
98 |
99 | ### quick add lines from draft
100 |
101 | > This Action will create a new task in Todoist for each line in the current draft. The content of each line will be parsed by todoist, so you can e.g. use dates or project/label notation in each lines just like you normally would directly in Todoist.
102 | >
103 | > After Success Setting: Trash
104 |
105 | ### quick add lines from prompt
106 |
107 | > This Action will create a new task in Todoist for each line you type into the displayed prompt. The content of each line will be parsed by todoist, so you can e.g. use dates or project/label notation in each lines just like you normally would directly in Todoist.
108 | >
109 | > After Success Setting: Nothing
110 |
111 | ## Create Single Task
112 |
113 | ### task with description from draft
114 |
115 | > This Action creates a task in the inbox of Todoist with the body of the draft as description. This means that the first line of the current draft will be used as the content of the task and everything else will be used as the description.
116 | >
117 | > After Success Setting: Trash
118 |
119 | ### task with description from prompt
120 |
121 | > This Action creates a task in the inbox of Todoist with a description from the text you type into the displayed prompt. The first line of the text in the prompt will be used as the content of the task and everything else will be used as the description.
122 | >
123 | > After Success Setting: Nothing
124 |
125 | ### task with description & settings from draft
126 |
127 | > This Action creates a task from the title (first line) of the current draft in Todoist with settings (due date, project, labels) from prompts and the body of the draft as description. This means that the first line of the current draft will be used as the content of the task and everything else will be used as the description.
128 | >
129 | > After Success Setting: Trash
130 |
131 | ### task with description & settings from prompt
132 |
133 | > This Action creates a task in Todoist with content, description and settings (due date, project, labels) from prompts. The first line of the text in the "add task with description & settings" prompt will be used as the content of the task and everything else will be used as the description.
134 | >
135 | > After Success Setting: Nothing
136 |
137 | ## Create (cross)Linked Task From Draft
138 |
139 | ### create linked task in inbox
140 |
141 | > This Action creates a task in the inbox of Todoist with the displayed title of the current draft as content. The created Task will contain a clickable link to directly open the linked draft.
142 | >
143 | > After Success Setting: Nothing
144 |
145 | ### create linked task with settings
146 |
147 | > This Action creates a task with settings (due date, project, labels) from prompts with the displayed title of the current draft as content. The created Task will contain a clickable link to directly open the linked draft.
148 | >
149 | > After Success Setting: Nothing
150 |
151 | ### create crosslinked task in inbox
152 |
153 | > This Action creates a crosslinked task in the inbox of Todoist with the displayed title of the current draft as content. The created Task will contain a clickable link to directly open the linked draft. The link to the created task will be added between the title and the body of the current draft. The added link types (app, web) can be configured with the `Draftist Settings` Action.
154 | >
155 | > After Success Setting: Nothing
156 |
157 | ### create crosslinked task with settings
158 |
159 | > This Action creates a crosslinked task with settings (due date, project, labels) from prompts in the inbox of Todoist with the displayed title of the current draft as content. The created Task will contain a clickable link to directly open the linked draft. The link to the created task will be added between the title and the body of the current draft. The added link types (app, web) can be configured with the `Draftist Settings` Action.
160 | >
161 | > After Success Setting: Nothing
162 |
163 | ## Create Multiple tasks
164 |
165 | ### tasks with same settings from draft
166 |
167 | > This Action creates a task for each line in the current draft. Each task will use the same settings (due date, project, labels) you select in the displayed prompts.
168 | >
169 | > After Success Setting: Trash
170 |
171 | ### tasks with same settings from prompt
172 |
173 | > This Action will create a new task in Todoist for each line you type into the displayed prompt. Each task will use the same settings (due date, project, labels) you select in the displayed prompts.
174 | >
175 | > After Success Setting: Nothing
176 |
177 | ### tasks with individual settings from draft
178 |
179 | > This Action creates a task for each line in the current draft. The settings (due date, project, label) can be set individually for each task in displayed prompts
180 | >
181 | > After Success Setting: Trash
182 |
183 | ### tasks with individual settings from prompt
184 |
185 | > This Action creates a task for each line you type in the first prompt. The settings (due date, project, label) can be set individually for each task in displayed prompts
186 | >
187 | > After Success Setting Nothing
188 |
189 | ## Create Tasks from MD Tasks in Draft
190 |
191 | ### quick add tasks from md tasks in draft
192 |
193 | > This Action will create a new task for each md task ("- [ ]") in the current draft. Similar to the [quick add lines from draft](#quick-add-lines-from-draft) the contents of the tasks will be parsed by Todoist.
194 | >
195 | > After Success Setting: Noting
196 |
197 | ### tasks with same settings from md tasks in draft
198 |
199 | > This Action will create tasks with identical settings for each md task ("- [ ]") in the current draft. The Action will display prompts to select the settings (due date, labels, project)
200 | >
201 | > After Success Setting: Nothing
202 |
203 | ### tasks with individual settings from md tasks in draft
204 |
205 | > This Action will create tasks with individual settings for each md task ("- [ ]") in the current draft. The Action will display prompts for each task to select the individual settings (due date, labels, project)
206 | >
207 | > After Success Setting: Nothing
208 |
209 | ## Import Tasks
210 |
211 | ### import todays tasks
212 |
213 | > This Action imports the tasks due today and appends them to the current draft. The meta information included for each task can be configured with the `Draftist Settings` Action.
214 | >
215 | > After Success Setting: Nothing
216 |
217 | ### import tasks from project
218 |
219 | > This Action imports the tasks from the configured project and appends them to the current draft. The meta information included for each task can be configured with the `Draftist Settings` Action. To configure the project which should be used by the Action, edit the "Define Template Tag" step and change the Template from "UNCONFIGURED" to the project name (best you copy the name from your Todoist project).
220 | >
221 | > After Success Setting: Nothing
222 |
223 | ### import tasks from selected project
224 |
225 | > This Action imports the tasks from the selected project and appends them to the current draft. The meta information included for each task can be configured with the `Draftist Settings` Action.
226 | >
227 | > After Success Setting: Nothing
228 |
229 | ### import tasks with all configured label(s)
230 |
231 | > This Action imports tasks which contain all configured labels and appends them to the current draft. The meta information included for each task can be configured with the `Draftist Settings` Action. To configure the labels which should be used by the Action, edit the "Define Template Tag" step and change the Template from "UNCONFIGURED" to the label names (best you copy the names from your Todoist labels) and separate them by commas with no spaces in between.
232 | >
233 | > After Success Setting: Nothing
234 |
235 | ### import tasks with any configured label(s)
236 |
237 | > This Action imports tasks which contain any configured label and appends them to the current draft. The meta information included for each task can be configured with the `Draftist Settings` Action. To configure the labels which should be used by the Action, edit the "Define Template Tag" step and change the Template from "UNCONFIGURED" to the label names (best you copy the names from your Todoist labels) and separate them by commas with no spaces in between.
238 | >
239 | > After Success Setting: Nothing
240 |
241 | ### import tasks with all selected label(s)
242 |
243 | > This Action imports tasks which contain all selected labels and appends them to the current draft. The meta information included for each task can be configured with the `Draftist Settings` Action.
244 | >
245 | > After Success Setting: Nothing
246 |
247 | ### import tasks with any selected label(s)
248 |
249 | > This Action imports tasks which contain any selected label and appends them to the current draft. The meta information included for each task can be configured with the `Draftist Settings` Action.
250 | >
251 | > After Success Setting: Nothing
252 |
253 | ### import tasks from filter
254 |
255 | > This Action imports tasks for the configured filter and appends them to the current draft. The meta information included for each task can be configured with the `Draftist Settings` Action. To configure the used filter , edit the "Define Template Tag" step and change the Template from "UNCONFIGURED" to the filter query you want to use (best you test the filter in todoist and then copy it into the template step).
256 | >
257 | > After Success Setting: Nothing
258 |
259 | ### import tasks from filter in prompt
260 |
261 | > This Action imports tasks for the filter query provided into the text field of the presented prompt. The meta information included for each task can be configured with the `Draftist Settings` Action.
262 | >
263 | > After Success Setting: Nothing
264 |
265 | ## Modify Tasks
266 |
267 | ### update labels of selected tasks from filter
268 |
269 | > This Action updates the labels of the selected tasks for the configured filter. To configure the used filter , edit the "Define Template Tag" step and change the Template from "UNCONFIGURED" to the filter query you want to use (best you test the filter in todoist and then copy it into the template step). This is e.g. useful if you use labels like "@LATER" and "@NEXT" to define the next steps in projects.
270 | > The Action presents several prompts. In the first one you'll see the returned tasks for the filter and can select the ones you want to update. The next prompt will als you for the labels you want to remove (if no selected task has any label, this prompt will not be presented). The last prompt lets you choose the labels you want to add to all selected tasks. If you only want to remove/add labels, just don't select any label in the corresponding prompt.
271 | >
272 | > After Success Setting: Nothing
273 |
274 | ### duplicate selected tasks with other label
275 |
276 | > This Action duplicates the selected tasks and changes a configurable source label to a destination label. This means that if you e.g. have a label "@waiting4" and you want follow up one or more of them, you can use this Action to e.g. create duplicates of the tasks you select with the destination label "@followUp" (the duplicates will not contain the ”@waiting4" label anymore).
277 | > To configure the labels used by this Action you need to edit the two "Define Template Tag" steps and change the Template from "UNCONFIGURED" to the name of the source / destination label you want to use.
278 | > The Action presents a prompt showing the tasks which contain the configured source label. You can check all the tasks you want to duplicate with the destination label.
279 | >
280 | > After Success Setting: Nothing
281 |
282 | ### update individual due date of selected task from filter
283 |
284 | > This Action updates the individual due date of the selected tasks from a configurable filter. The Action lets the user select tasks from the configured filter and present a prompt for each selected task to select a new due date.
285 | > To configure the used filter , edit the "Define Template Tag" step and change the Template from "UNCONFIGURED" to the filter query you want to use (best you test the filter in todoist and then copy it into the template step). This Action may be useful to e.g. reschedule tasks from the filter "overdue,today", or schedule tasks from a label e.g. if you filter for "@thisWEEK".
286 | >
287 | > After Success Setting: Nothing
288 |
289 | ### update due date of selected task from filter to same date
290 |
291 | > This Action updates the due date of the selected tasks from a configurable filter to the same selected due date. The Action lets the user select tasks from the configured filter and present a prompt for the due date which will be applied to all selected tasks
292 | > To configure the used filter , edit the "Define Template Tag" step and change the Template from "UNCONFIGURED" to the filter query you want to use (best you test the filter in todoist and then copy it into the template step). This Action may be useful to e.g. reschedule tasks from the filter "overdue,today" to the next day.
293 | >
294 | > After Success Setting: Nothing
295 |
296 | ### resolve selected tasks from filter
297 |
298 | > This Action resolves the selected tasks from a configurable filter. The Action presents a prompt with all tasks returned for the configured filter. Each selected task will be resolved (marked as completed).
299 | > To configure the used filter , edit the "Define Template Tag" step and change the Template from "UNCONFIGURED" to the filter query you want to use (best you test the filter in todoist and then copy it into the template step). This Action may be useful to e.g. quickly resolve several tasks with a label or tasks from "today".
300 | >
301 | > After Success Setting: Nothing
302 |
303 | ### delete selected tasks from filter
304 |
305 | > This Action deletes the selected tasks from a configurable filter. The Action presents a prompt with all tasks returned for the configured filter. Each selected task will be deleted from your Todoist Account.
306 | > To configure the used filter , edit the "Define Template Tag" step and change the Template from "UNCONFIGURED" to the filter query you want to use (best you test the filter in todoist and then copy it into the template step). This Action may be useful to e.g. quickly cleaning up your Inbox by using the filter "#inbox".
307 | >
308 | > After Success Setting: Nothing
309 |
--------------------------------------------------------------------------------
/Draftist Instructions.md:
--------------------------------------------------------------------------------
1 | # Draftist Action Group Instructions
2 |
3 | **created by [@FlohGro](https://mobile.twitter.com/FlohGro)**
4 |
5 | - **Website:** [flohgro.com](https://flohgro.com)
6 | - **Drafts Forums:** [@FlohGro](https://forums.getdrafts.com/u/flohgro/summary)
7 | - **Twitter:** [@FlohGro](https://twitter.com/FlohGro)
8 |
9 | > Welcome to Draftist - an Action Group for Draft to integrate with Todoist. This might be the most advanced Action Group to use Drafts with Todoist so please read the instructions.
10 | > Before running any other Action, you have to run the "Draftist Setup/Update" Action which downloads the latest version of Draftist from the repository
11 |
12 | Draftist contains a big amount of Actions to integrate Drafts with Todoist.
13 |
14 | The Actions are divided into the following sections:
15 |
16 | - General
17 | - Quick Add Tasks
18 | - Create Single Task
19 | - Create (cross)Linked Task From Draft
20 | - Create Multiple Tasks
21 | - Create Tasks from MD Tasks in Draft
22 | - Import Tasks
23 | - Modify Tasks
24 |
25 | Before running any Action in this Action Group, make sure to Setup the `Draftist.js` file in your directory by running the `Draftist Setup/Update` Action.
26 |
27 | ## Youtube Videos
28 |
29 | Here I'll collect Demos on Youtube about Draftist to get you started easier.
30 |
31 | - [Draftist Introduction & Setup](https://youtu.be/gHObL8GqThM)
32 |
33 | ## Using Draftist
34 |
35 | Since Draftist contains a lot of Actions and you may not need every single of them you may want to create your own `my Draftist` Action Group to only include the Actions you want.
36 | While you can duplicate / move the Actions you use to your own Action Group(s) I recommend to leave the Draftist Action Group untouched. This ensures a single point of truth and if I need to fix/maintain Actions in Draftist your own Actions will work afterwards. Instead you can use the `Draftist Action Replicator` Action to reinstall an existing Draftist Action to your `my Draftist` Action Group. When you replicated a Draftist Action you can then give your own action a (shorter) name than in the Draftist Action Group (especially on mobile devices the names of some actions are too long to distinguish them easily.).
37 | This is also useful if you want to use Actions with configuration options (to e.g. retrieve tasks from a filter) with different configurations. Then you can use the `Draftist Action Replicator` to replicate the same Action from Draftist several times and configure them to your needs.
38 | You can assign keyboard shortcuts to the Actions you created to make them easily accessible (none of the Actions in Draftist has a keyboard shortcut assigned to don't conflict with your assignments).
39 |
40 | Please **never** rename the `Draftist` Action at the top of the Action Group (If you do so, no other Action will work anymore).
41 |
42 | Draftist is not displayed as Action Bar (over the keyboard) by default since (again) it contains a lot of Actions which sometimes use the same icons. If you want access to the Actions in the Action Bar - again i recommend to replicate the subset of Draftist Actions you need into your own Action Group and then display these Actions in the Action Bar.
43 |
44 | ## Action Descriptions
45 |
46 | Every Draftist Action contains a short description about its purpose / what it does. Due to the big amount of Actions you can also read through all descriptions in the [Action Descriptions](https://github.com/FlohGro-dev/Draftist/blob/main/Action%20Descriptions.md) file.
47 |
48 | ## Customizing Actions
49 |
50 | All Draftist Actions work out of the box with no needed configuration from you. Especially the more complex and powerful actions allow you to configure the behavior of them in the action steps. Read through the corresponding [Action Description](https://github.com/FlohGro-dev/Draftist/blob/main/Action%20Descriptions.md) to find instructions what and how you can configure the Action to your need.
51 |
52 | ## Support Development
53 |
54 | Draftist is completely free to use for you. However if this Action Group is useful for you and supports your workflows you can give something back to support development.
55 | I enjoy a good coffee ☕️ (weather at home or in an actual coffee shop) and love pizza 🍕.
56 | You can choose the amount you want to donate on those platforms.
57 |
58 |
59 |
60 |
61 |
62 | ## Feature Requests and Issue Reporting
63 |
64 | If you encounter any issues or have additional feature requests you can reach out to me in different ways:
65 |
66 | - report / request issues in the GitHub repository [here](https://github.com/FlohGro-dev/Draftist/issues)
67 | - take part in the conversation in the Drafts forums [here](https://forums.getdrafts.com/t/draftist-a-drafts-action-group-for-todoist/12674)
68 | - contact me on other platforms of your choice [here](https://flohgro.com/contactme)
69 |
70 | ## Created Files
71 |
72 | Draftist will create three new files in your iCloud Drive folder at the path `.../Drafts/Library/Scripts`:
73 |
74 | - `DraftistDataStore.json`: Draftist Todoist Data Store
75 | - this file stores data from your Todoist account which is used by several actions
76 | - the data includes:
77 | - projects (and their metadata)
78 | - sections (and their metadata)
79 | - labels (and their metadata)
80 | - none of this data leaves your iCloud Account, it is synced in the Drafts directory - it's just used to not always request the data from Todoists API which slows down the process of e.g. creating tasks with settings a lot.
81 | - `DraftistSettings.json`: Draftist Action Group Settings
82 | - this file stores the settings you can modify with the [Draftist Settings Action](https://github.com/FlohGro-dev/Draftist/blob/main/Action%20Descriptions.md#Draftist%20Settings)
83 | - `Draftist.js`: Draftist Functions
84 | - this file contains the functions all the Draftist Actions use under the hood. This enables the update process without the need to reinstall the complete Action Group.
85 |
86 | I don't recommend to delete these files unless you have issues using Draftist. If you delete the `Draftist.js` no Action in the Draftist Action Group will work anymore until you reinstall the file. The Settings and Data Store file will be recreated automatically.
87 |
88 | ## Changelog
89 |
90 | To stay up to date on new updates you can follow me on [Twitter](https://twitter.com/FlohGro) or take part in the converstaion in the [Drafts forums post](https://forums.getdrafts.com/t/draftist-a-drafts-action-group-for-todoist/12674)
91 | You can find a changelog of updates to Action Group in the [Changelog](https://github.com/FlohGro-dev/Draftist#changelog) section of the README in this repository.
92 |
--------------------------------------------------------------------------------
/Draftist.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Draftist Action Group Functions
3 | * @author FlohGro
4 | * @copyright 2022, FlohGro
5 | * @licensing MIT free to use - but donate coffees to support development http://www.flohgro.com/donate
6 | * @version 0.2
7 | */
8 |
9 | /**
10 | * Draftist_checkTodoistForError - This function checks the provided Todoist Object for errors.
11 | * If an error was detected it will be returned, otherwise undefined will be returned.
12 | * @param {Todoist_Object} todoist_obj the todoist object to check
13 | * @returns {error | undefined} a present error or undefined
14 | */
15 | function Draftist_getLastTodoistError(todoistObj) {
16 | let error = todoistObj.lastError
17 | if (error) {
18 | return error;
19 | } else {
20 | return undefined;
21 | }
22 | }
23 |
24 | /**
25 | * Draftist_succeedAction - notifies the user about a successful execution of an action
26 | *
27 | * @param {String} actionName the name of the action (might be empty if not displayed)
28 | * @param {Boolean} displayActionName bool if the provided name of the action shall be displayed or not
29 | * @param {String} successMessage the content for the success message
30 | */
31 | function Draftist_succeedAction(actionName, displayActionName, successMessage) {
32 | app.displaySuccessMessage((displayActionName ? actionName + " succeeded: " : "") + successMessage);
33 | }
34 |
35 | /**
36 | * Draftist_cancelAction - This function notifies the user which action was canceled and why.
37 | * @param {String} actionName the name of the actionName
38 | * @param {String} cancelReasonDescription description why the action was cancelled
39 | */
40 | function Draftist_cancelAction(actionName, cancelReasonDescription) {
41 | context.cancel(actionName + " was cancelled: " + cancelReasonDescription);
42 | app.displayWarningMessage(actionName + " was cancelled: " + cancelReasonDescription);
43 | }
44 |
45 | /**
46 | * Draftist_failAction - This function notifies the user when an action failed and why.
47 | * @param {String} actionName the name of the actionName
48 | * @param {String} failedReasonDescription description why the action failed
49 | */
50 | function Draftist_failAction(actionName, failedReasonDescription) {
51 | context.fail(actionName + " failed: " + failedReasonDescription);
52 | alert(actionName + " failed: " + failedReasonDescription);
53 | }
54 |
55 | /**
56 | * Draftist_infoMessage - displays a info message to the user prepended with "Draftist: "
57 | *
58 | * @param {String} actionName the name of the action (might be empty if not displayed)
59 | * @param {String} successMessage the content for the info message
60 | */
61 | function Draftist_infoMessage(actionName, infoMessage) {
62 | app.displayInfoMessage("Draftist: " + infoMessage + (actionName.length > 0 ? "(" + actionName + ")" : ""));
63 | }
64 |
65 | /**
66 | * Draftist_quickAdd - This function adds the provided string to Todoist using the quickAdd API.
67 | * This supports the Todoist natural language which will be processed by Todoist automatically.
68 | *
69 | * @param {Todoist_Object|undefined} todoist_obj - if already created, otherwise the function will create its own.
70 | * @param {string} content the task content as string
71 | * @return {Boolean} true when added successfully, false when adding task failed
72 | */
73 | function Draftist_quickAdd({
74 | todoist = new Todoist(),
75 | content
76 | }) {
77 | if (!todoist.quickAdd(content)) {
78 | let error = Draftist_getLastTodoistError(todoist)
79 | let errorMsg = "adding tasks failed, todoist returned:\n" + error
80 | Draftist_failAction("Quick Add", errorMsg)
81 | return false;
82 | } else {
83 | return true;
84 | }
85 | }
86 |
87 | /**
88 | * Draftist_createTask - creates a Task with the given parameters
89 | *
90 | * @param {Todoist_Object} todoist_obj? - if already created, otherwise the function will create its own.
91 | * @param {String} content: Task content. This value may contain markdown-formatted text and hyperlinks. Details on markdown support can be found in the Text Formatting article in the Todoist Help Center.
92 | * @param {String} description?: A description for the task. This value may contain markdown-formatted text and hyperlinks. Details on markdown support can be found in the Text Formatting article in the Todoist Help Center.
93 | * @param {String} project_id?: Task project ID. If not set, task is put to user's Inbox.
94 | * @param {String} section_id?: ID of section to put task into.
95 | * @param {String} parent_id?: Parent task ID.
96 | * @param {Integer} order?: Non-zero integer value used by clients to sort tasks under the same parent.
97 | * @param {String[]} labels?: names of labels associated with the task.
98 | * @param {Ingeger} priority?: Task priority from 1 (normal) to 4 (urgent).
99 | * @param {String} due_string?: No Human defined task due date (ex.: "next Monday", "Tomorrow"). Value is set using local (not UTC) time.
100 | * @param {String} due_date?: Specific date in YYYY-MM-DD format relative to user’s timezone.
101 | * @param {String} due_datetime?: Specific date and time in RFC3339 format in UTC.
102 | * @param {String} due_lang?: 2-letter code specifying language in case due_string is not written in English.
103 | * @param {Integer} assignee_id?: The responsible user ID (if set, and only for shared tasks).
104 | * @param {Boolean} getTaskResult?: if set to true, the function will return the api response of the created Task
105 | * @return {Boolean} true when added successfully, false when adding task failed
106 | */
107 | function Draftist_createTask({
108 | todoist = new Todoist(),
109 | content,
110 | description = "",
111 | project_id = undefined,
112 | section_id = undefined,
113 | parent_id = undefined,
114 | order = undefined,
115 | labels = [],
116 | priority = undefined,
117 | due_string = undefined,
118 | due_date = undefined,
119 | due_datetime = undefined,
120 | due_lang = undefined,
121 | assignee = undefined
122 | }, getTaskResult = false) {
123 | // check if provided content is not empty
124 | if (content.length == 0) {
125 | Draftist_failAction("create Task", "no task content provided")
126 | return false;
127 | }
128 |
129 | let taskMap = new Map();
130 | taskMap.set("content", content);
131 | taskMap.set("description", description);
132 | if (project_id) {
133 | taskMap.set("project_id", project_id);
134 | }
135 | if (section_id) {
136 | taskMap.set("section_id", section_id);
137 | }
138 | if (parent_id) {
139 | taskMap.set("parent_id", parent_id);
140 | }
141 | if (order) {
142 | taskMap.set("order", order);
143 | }
144 | if (labels.length > 0) {
145 | taskMap.set("labels", labels);
146 | }
147 | if (priority) {
148 | taskMap.set("priority", priority);
149 | }
150 | if (due_string) {
151 | taskMap.set("due_string", due_string);
152 | }
153 | if (due_date) {
154 | taskMap.set("due_date", due_date);
155 | }
156 | if (due_datetime) {
157 | taskMap.set("due_datetime", due_datetime);
158 | }
159 | if (due_lang) {
160 | taskMap.set("due_lang", due_lang);
161 | }
162 | if (assignee) {
163 | taskMap.set("assignee", assignee);
164 | }
165 |
166 | let taskObj = Object.fromEntries(taskMap)
167 |
168 | let taskCreateResult = todoist.createTask(taskObj)
169 | if (taskCreateResult) {
170 | if (getTaskResult) {
171 | return taskCreateResult;
172 | } else {
173 | return true;
174 | }
175 | } else {
176 | Draftist_failAction("create Task", Draftist_getLastTodoistError(todoist))
177 | return false;
178 | }
179 | }
180 |
181 |
182 | /**
183 | * Draftist_quickAddLines - This action creates a new task for each line in the currently open Draft
184 | *
185 | * @param {String} text the text which lines should be added as tasks to Todoist
186 | * @return {Boolean|Number} false when adding faile, task number when adding succeeded
187 | */
188 | function Draftist_quickAddLines(text) {
189 | let todoist = new Todoist()
190 | let lines = text.split("\n");
191 | let createdTasksCounter = 0;
192 | // repeat for each line
193 | for (line of lines) {
194 | if (line.length !== 0) {
195 | if (!Draftist_quickAdd({
196 | todoist: todoist,
197 | content: line
198 | })) {
199 | // if failed directly return, quickadd will display the error
200 | return false;
201 | } else {
202 | createdTasksCounter++;
203 | }
204 | }
205 | }
206 | return createdTasksCounter;
207 | }
208 |
209 |
210 |
211 | // Create Tasks Actions
212 |
213 | /**
214 | * Draftist_quickAddLinesFromCurrentDraft - use todoist quick add for each line of the current draft
215 | *
216 | * @return {Boolean} true when added successfully, false when adding task failed
217 | */
218 | function Draftist_quickAddLinesFromCurrentDraft() {
219 | if (draft.content.length == 0) {
220 | Draftist_cancelAction("Add Tasks from current Draft", "Draft is blank")
221 | return false;
222 | } else {
223 | let taskNumber = Draftist_quickAddLines(draft.content);
224 | if (taskNumber) {
225 | // succeeded
226 | Draftist_succeedAction("", false, "successfully added " + taskNumber + " tasks :)")
227 | return true;
228 | } else {
229 | return false;
230 | }
231 | }
232 | }
233 |
234 |
235 | /**
236 | * Draftist_quickAddLinesFromPrompt - use todoist quick add for each line inserted in the prompt
237 | *
238 | * @return {Boolean} true when added successfully, false when adding task failed
239 | */
240 | function Draftist_quickAddLinesFromPrompt() {
241 | let p = new Prompt();
242 | p.title = "add tasks from lines";
243 | p.addTextView("tasks", "", "", {
244 | wantsFocus: true
245 | });
246 | p.addButton("add tasks");
247 |
248 | if (!p.show()) {
249 | Draftist_cancelAction("Add Tasks from Prompt", "cancelled by user");
250 | return false;
251 | }
252 | // user did select "add tasks"
253 | let input = p.fieldValues["tasks"];
254 | if (input.length == 0) {
255 | Draftist_cancelAction("Add Tasks from Prompt", "No input provided")
256 | return false;
257 | } else {
258 | let taskNumber = Draftist_quickAddLines(input);
259 | if (taskNumber) {
260 | // succeeded
261 | Draftist_succeedAction("", false, "successfully added " + taskNumber + " tasks :)")
262 | return true;
263 | } else {
264 | return false;
265 | }
266 | }
267 | }
268 |
269 | /**
270 | * Draftist_createTaskWithDescription - reate a task with the first line of the input as content and everything else as description
271 | *
272 | * @param {String} text the text which should be used for the task and description
273 | * @return {Boolean} true when added successfully, false when adding task failed
274 | */
275 | function Draftist_createTaskWithDescription(text) {
276 | let lines = text.split("\n");
277 | // first line is the tasks content, remove it from the array and assign it
278 | let content = lines.shift();
279 | let description = lines.join("\n")
280 | return Draftist_createTask({
281 | content: content,
282 | description: description
283 | });
284 | }
285 |
286 | /**
287 | * Draftist_createTaskWithDescriptionFromCurrentDraft - add task with description from current draft. use the first line as content and everything else as description
288 | *
289 | * @return {Boolean} true when added successfully, false when adding task failed
290 | */
291 | function Draftist_createTaskWithDescriptionFromCurrentDraft() {
292 | if (draft.content.length == 0) {
293 | Draftist_cancelAction("Task with description from current Draft", "Draft is blank")
294 | return false;
295 | } else {
296 | let taskCreated = Draftist_createTaskWithDescription(draft.content);
297 | if (taskCreated) {
298 | // succeeded
299 | Draftist_succeedAction("", false, "successfully added task");
300 | return true;
301 | } else {
302 | return false
303 | }
304 | }
305 | }
306 |
307 | /**
308 | * Draftist_createTaskWithDescriptionFromPrompt - add task with description from prompt. use the first line as content and everything else as description
309 | *
310 | * @return {Boolean} true when added successfully, false when adding task failed
311 | */
312 | function Draftist_createTaskWithDescriptionFromPrompt() {
313 | let p = new Prompt();
314 | p.title = "add tasks with description";
315 | p.message = "first line is the tasks content; everything else will be used as description"
316 | p.addTextView("task", "", "", {
317 | wantsFocus: true
318 | });
319 | p.addButton("add task");
320 |
321 | if (!p.show()) {
322 | Draftist_cancelAction("Add Tasks from Prompt", "cancelled by user");
323 | return false;
324 | }
325 | // user did select "add tasks"
326 | let input = p.fieldValues["task"];
327 | if (input.length == 0) {
328 | Draftist_cancelAction("Add Tasks from Prompt", "No input provided")
329 | return false;
330 | } else {
331 | let taskCreated = Draftist_createTaskWithDescription(input);
332 | if (taskCreated) {
333 | // succeeded
334 | Draftist_succeedAction("", false, "successfully added task");
335 | return true;
336 | } else {
337 | return false
338 | }
339 | }
340 | }
341 |
342 | // #############################################################################
343 | // CREATE TASK OBJECT
344 | // #############################################################################
345 |
346 | /**
347 | * Draftist_createTaskObjectWithSettingsFromPrompt - creates a todoist task object with settings from prompts
348 | *
349 | * @param {String} content content of the task (must not be empty)
350 | * @param {String} description? description for the task (can be empty)
351 | * @return {taskObject} taskObject for a todoist task which can be passed to the Todoist.createTask() API of Drafts
352 | */
353 | function Draftist_createTaskObjectWithSettingsFromPrompt(content, description = "") {
354 | // check if any map of the todoist data contains data - if not, load the data into the vars
355 | if (projectsNameToIdMap.size == 0) {
356 | Draftist_getStoredTodoistData();
357 | }
358 |
359 | // due date prompt
360 | let pDate = new Prompt()
361 | pDate.title = "select due date for \"" + content + "\":";
362 | pDate.addButton("today");
363 | pDate.addButton("tomorrow");
364 | pDate.addButton("next week");
365 | pDate.addButton("other");
366 | pDate.addButton("no due date", undefined)
367 | pDate.isCancellable = false;
368 | pDate.show()
369 | // if buttonPressed is undefined no due date was selected
370 | const dateIsSet = (pDate.buttonPressed ? true : false);
371 | let selectedDateString = undefined;
372 | if (dateIsSet) {
373 | if (pDate.buttonPressed == "other") {
374 | var pSelDate = new Prompt();
375 | pSelDate.title = "select custom date for \"" + content + "\":";
376 | var today = new Date();
377 | var tomorrow = new Date(new Date().setDate(new Date().getDate() + 1));
378 | pSelDate.addDatePicker("dueDatePicker", "", tomorrow, {
379 | "mode": "date",
380 | "minimumDate": tomorrow
381 | });
382 |
383 | pSelDate.addButton("set due date");
384 | pSelDate.isCancellable = false;
385 | pSelDate.show();
386 | let pickedDueDate = pSelDate.fieldValues["dueDatePicker"];
387 | let day = pickedDueDate.getDate();
388 | let month = pickedDueDate.getMonth() + 1;
389 | let year = pickedDueDate.getFullYear();
390 | selectedDateString = String(year) + "-" + String(month) + "-" + String(day);
391 | } else {
392 | selectedDateString = pDate.buttonPressed;
393 | }
394 | }
395 |
396 | // priority prompt
397 | let pPrio = new Prompt();
398 | pPrio.title = "select priority for \"" + content + "\":";
399 | pPrio.addButton("p1");
400 | pPrio.addButton("p2");
401 | pPrio.addButton("p3");
402 | pPrio.addButton("p4");
403 | pPrio.isCancellable = false;
404 | pPrio.show();
405 |
406 | // the api of todoist uses different numbering than the user sees. p1 is reflected as value 4, p2 -> 3 and so on -> store this value
407 | let selectedPriority = 0;
408 | switch (pPrio.buttonPressed) {
409 | case "p1":
410 | selectedPriority = 4;
411 | break;
412 | case "p2":
413 | selectedPriority = 3;
414 | break;
415 | case "p3":
416 | selectedPriority = 2;
417 | break;
418 | case "p4":
419 | selectedPriority = 1;
420 | break;
421 | }
422 |
423 |
424 | // project prompt
425 | let pProject = new Prompt();
426 | pProject.title = "select project for \"" + content + "\":";
427 | pProject.message = "select Inbox if you want to sort it later"
428 |
429 | let sortedProjectNameMap = new Map([...projectsNameToIdMap].sort((a, b) => String(a[0]).localeCompare(b[0])))
430 |
431 |
432 |
433 | let inboxProject = sortedProjectNameMap.get("Inbox")
434 | let teamInboxProject = sortedProjectNameMap.get("Team Inbox")
435 |
436 | if (inboxProject) {
437 | pProject.addButton("Inbox", inboxProject);
438 | }
439 |
440 | if (teamInboxProject) {
441 | pProject.addButton("Team Inbox", teamInboxProject);
442 | }
443 |
444 | for (const [pName, pId] of sortedProjectNameMap) {
445 | if (pId != inboxProject && pId != teamInboxProject) {
446 | // selected button will directly contain the projects id as value
447 | pProject.addButton(pName, pId);
448 |
449 | }
450 | }
451 |
452 | pProject.isCancellable = false;
453 | pProject.show();
454 | let selectedProjectId = pProject.buttonPressed;
455 |
456 | // labels prompt
457 | let pLabels = new Prompt();
458 | pLabels.title = "select labels for \"" + content + "\":";
459 |
460 | //TODO
461 |
462 |
463 | let sortedLabelsNameMap = new Map([...labelsNameToIdMap].sort((a, b) => String(a[0]).localeCompare(b[0])))
464 |
465 | pLabels.addSelect("labels", "select labels", Array.from(sortedLabelsNameMap.keys()), [], true);
466 | pLabels.addButton("set labels");
467 | pLabels.isCancellable = false;
468 | pLabels.show();
469 | let selectedLabels = pLabels.fieldValues["labels"];
470 |
471 | let taskObject = {
472 | content: content,
473 | description: description,
474 | project_id: selectedProjectId,
475 | section_id: undefined,
476 | labels: selectedLabels,
477 | priority: selectedPriority,
478 | due_string: (selectedDateString ? selectedDateString : undefined),
479 | }
480 |
481 | return taskObject;
482 |
483 | }
484 |
485 | // #############################################################################
486 | // CREATE TASKS WITH SETTINGS
487 | // #############################################################################
488 |
489 | /**
490 | * Draftist_createTaskWithDescriptionAndSettings - create a task with description and settings (project, labels, due date) from prompts. first line will be used as task content, everything else will be the description
491 | *
492 | * @param {String} text the text which should be used for the task and description
493 | * @return {Boolean} true when added successfully, false when adding task failed
494 | */
495 | function Draftist_createTaskWithDescriptionAndSettings(text) {
496 | let lines = text.split("\n");
497 | // first line is the tasks content, remove it from the array and assign it
498 | let content = lines.shift();
499 | let description = lines.join("\n")
500 | return Draftist_createTask(Draftist_createTaskObjectWithSettingsFromPrompt(content, description));
501 | }
502 |
503 | /**
504 | * Draftist_createTaskWithDescriptionAndSettingsFromCurrentDraft - add task with description and settings from prompt from current draft. use the first line as content and everything else as description
505 | *
506 | * @return {Boolean} true when added successfully, false when adding task failed
507 | */
508 | function Draftist_createTaskWithDescriptionAndSettingsFromCurrentDraft() {
509 | if (draft.content.length == 0) {
510 | Draftist_cancelAction("Task with description from current Draft", "Draft is blank")
511 | return false;
512 | } else {
513 | let taskCreated = Draftist_createTaskWithDescriptionAndSettings(draft.content);
514 | if (taskCreated) {
515 | // succeeded
516 | Draftist_succeedAction("", false, "successfully added task");
517 | return true;
518 | } else {
519 | return false
520 | }
521 | }
522 | }
523 |
524 | /**
525 | * Draftist_createTaskWithDescriptionAndSettingsFromPrompt - add task with description and Settings from prompts. use the first line as content and everything else as description
526 | *
527 | * @return {Boolean} true when added successfully, false when adding task failed
528 | */
529 | function Draftist_createTaskWithDescriptionAndSettingsFromPrompt() {
530 | let p = new Prompt();
531 | p.title = "add task with description & settings";
532 | p.message = "first line is the tasks content; everything else will be used as description"
533 | p.addTextView("task", "", "", {
534 | wantsFocus: true
535 | });
536 | p.addButton("add task");
537 |
538 | if (!p.show()) {
539 | Draftist_cancelAction("Add Tasks from Prompt", "cancelled by user");
540 | return false;
541 | }
542 | // user did select "add tasks"
543 | let input = p.fieldValues["task"];
544 | if (input.length == 0) {
545 | Draftist_cancelAction("Add Tasks from Prompt", "No input provided")
546 | return false;
547 | } else {
548 | let taskCreated = Draftist_createTaskWithDescriptionAndSettings(input);
549 | if (taskCreated) {
550 | // succeeded
551 | Draftist_succeedAction("", false, "successfully added task");
552 | return true;
553 | } else {
554 | return false
555 | }
556 | }
557 | }
558 |
559 | // #############################################################################
560 | // CREATE LINKED TASKS
561 | // #############################################################################
562 |
563 | /**
564 | * Draftist_helperCreateMdLinkToCurrentDraft - creates a markdown link to the current draft in the editor
565 | *
566 | * @return {String} markdown link to the current open draft in the editor
567 | */
568 | function Draftist_helperCreateMdLinkToCurrentDraft() {
569 | return "[" + draft.displayTitle + "]" + "(" + draft.permalink + ")"
570 | }
571 |
572 |
573 | /**
574 | * Draftist_helperCreateOpenTaskUrlFromTaskObject - creates the weblink to the given task object
575 | *
576 | * @param {Todoist_Task} taskObject Todoist Task Object in JSON format
577 | * @return {String} web / mobile link(s) to the Todoist Task depending on the stored settings
578 | */
579 | function Draftist_helperCreateOpenTaskUrlFromTaskObject(taskObject) {
580 | // load settings
581 | Draftist_loadCurrentConfigurationSettings()
582 | const webLink = "[🌐](" + taskObject.url + ")";
583 | const mobileLink = "[📱](todoist://task?id=" + taskObject.id + ")";
584 | if (activeSettings["taskLinkTypes"].includes("web") && activeSettings["taskLinkTypes"].includes("app")) {
585 | return webLink + "\n" + mobileLink;
586 | } else if (activeSettings["taskLinkTypes"].includes("web") && !activeSettings["taskLinkTypes"].includes("app")) {
587 | return webLink;
588 | } else if (!activeSettings["taskLinkTypes"].includes("web") && activeSettings["taskLinkTypes"].includes("app")) {
589 | return mobileLink;
590 | }
591 | }
592 |
593 |
594 | /**
595 | * Draftist_createTaskInInboxWithLinkToDraft - creates a task in the Todoist Inbox containing the title and link to the current draft
596 | *
597 | * @return {Boolean} true if succeeded, otherwise false
598 | */
599 | function Draftist_createTaskInInboxWithLinkToDraft() {
600 | if (Draftist_createTask({
601 | content: Draftist_helperCreateMdLinkToCurrentDraft()
602 | })) {
603 | Draftist_succeedAction("", false, "added linked task");
604 | return true;
605 | } else {
606 | return false;
607 | }
608 | }
609 |
610 | /**
611 | * Draftist_createTaskWithSettingsAndLinkToDraft - creates a task in the Todoist with settings from prompts containing the title and link to the current draft
612 | *
613 | * @return {Boolean} true if succeeded, otherwise false
614 | */
615 | function Draftist_createTaskWithSettingsAndLinkToDraft() {
616 | let taskObject = Draftist_createTaskObjectWithSettingsFromPrompt(Draftist_helperCreateMdLinkToCurrentDraft());
617 | if (Draftist_createTask(taskObject)) {
618 | Draftist_succeedAction("", false, "added linked task with settings");
619 | return true;
620 | } else {
621 | return false;
622 | }
623 | }
624 |
625 |
626 | /**
627 | * Draftist_helperAddTextBetweenTitleAndBodyOfCurrentDraft - adds the provided text between the title and body of the current draft the added text will be surrounded by empty lines
628 | *
629 | * @param {type} textToAdd the text to add between title and body
630 | */
631 | function Draftist_helperAddTextBetweenTitleAndBodyOfCurrentDraft(textToAdd) {
632 | let lines = draft.content.split("\n");
633 | let curIndex = 1
634 | if (lines.length == 1) {
635 | // add two empty lines if draft has only one line
636 | lines.push("")
637 | lines.push("")
638 | }
639 | if (lines[curIndex].length != 0) {
640 | lines.splice(curIndex, 0, "");
641 | }
642 | curIndex++;
643 | lines.splice(curIndex, 0, textToAdd)
644 | curIndex++;
645 | if (lines[curIndex].length != 0) {
646 | lines.splice(curIndex, 0, "")
647 | }
648 | draft.content = lines.join("\n");
649 | draft.update()
650 | }
651 |
652 | /**
653 | * Draftist_createTaskInInboxWithLinkToDraft - creates a task in the Todoist Inbox containing the title and link to the current draft. A link to the created Task in Todoist will be added to the Draft between the title and the body
654 | *
655 | * @return {Boolean} true if succeeded, otherwise false
656 | */
657 | function Draftist_createCrosslinkedTaskInInbox() {
658 | let createdTask = Draftist_createTask({
659 | content: Draftist_helperCreateMdLinkToCurrentDraft()
660 | }, true)
661 | if (createdTask) {
662 | Draftist_helperAddTextBetweenTitleAndBodyOfCurrentDraft(Draftist_helperCreateOpenTaskUrlFromTaskObject(createdTask));
663 | Draftist_succeedAction("", false, "added linked task");
664 | return true;
665 | } else {
666 | return false;
667 | }
668 | }
669 |
670 | /**
671 | * Draftist_createCrosslinkedTaskWithSettings - creates a task in the Todoist with settings from prompts containing the title and link to the current draft. A link to the created Task in Todoist will be added to the Draft between the title and the body
672 | *
673 | * @return {Boolean} true if succeeded, otherwise false
674 | */
675 | function Draftist_createCrosslinkedTaskWithSettings() {
676 | let taskObject = Draftist_createTaskObjectWithSettingsFromPrompt(Draftist_helperCreateMdLinkToCurrentDraft());
677 | let createdTask = Draftist_createTask(taskObject, true)
678 | if (createdTask) {
679 | Draftist_helperAddTextBetweenTitleAndBodyOfCurrentDraft(Draftist_helperCreateOpenTaskUrlFromTaskObject(createdTask));
680 | Draftist_succeedAction("", false, "added linked task");
681 | return true;
682 | } else {
683 | return false;
684 | }
685 | }
686 |
687 | // #############################################################################
688 | // CREATE MULTIPLE TASKS
689 | // #############################################################################
690 |
691 | /**
692 | * Draftist_createTasksFromLinesWithIdenticalSettings - creates a task from each line in the passed text with identical settings from prompts
693 | *
694 | * @param {String} text the string containing the tasks seperated by new lines
695 | * @return {Boolean} true if added successfully; false if adding tasks failed
696 | */
697 | function Draftist_createTasksFromLinesWithIdenticalSettings(text) {
698 | if (text.length == 0) {
699 | return false;
700 | } else {
701 | let taskCount = 0;
702 | let taskBaseObject = Draftist_createTaskObjectWithSettingsFromPrompt("multiple tasks");
703 | let lines = text.split("\n");
704 | for (line of lines) {
705 | if (line.length != 0) {
706 | taskBaseObject.content = line
707 | if (Draftist_createTask(taskBaseObject)) {
708 | // increase task counter
709 | taskCount = taskCount + 1;
710 | } else {
711 | // stop adding tasks and return immideately
712 | return false;
713 | }
714 | }
715 | }
716 | // succeeded
717 | Draftist_succeedAction("", false, "successfully added " + taskCount + " task(s)");
718 | }
719 | }
720 |
721 |
722 | /**
723 | * Draftist_createTasksFromLinesInDraftWithIdenticalSettings - creates tasks for each line in the current draft with identical settings from prompts
724 | *
725 | * @return {Boolean} true if added successfully; false if adding tasks failed
726 | */
727 | function Draftist_createTasksFromLinesInDraftWithIdenticalSettings() {
728 | if (draft.content.length != 0) {
729 | return Draftist_createTasksFromLinesWithIdenticalSettings(draft.content);
730 | } else {
731 | Draftist_cancelAction("Tasks from lines in current Draft with identical settings", "Draft is blank")
732 | return false;
733 | }
734 | }
735 |
736 |
737 | /**
738 | * Draftist_createTasksFromLinesInPromptWithIdenticalSettings - creates tasks for each line in the displayed prompt with identical settings from prompts
739 | *
740 | * @return {Boolean} true when added successfully, false when adding task failed
741 | */
742 | function Draftist_createTasksFromLinesInPromptWithIdenticalSettings() {
743 | let p = new Prompt();
744 | p.title = "add tasks with same settings";
745 | p.message = "each line will be its own task - all use the same settings in the next prompts"
746 | p.addTextView("tasks", "", "", {
747 | wantsFocus: true
748 | });
749 | p.addButton("add tasks");
750 |
751 | if (!p.show()) {
752 | Draftist_cancelAction("Add Tasks from Prompt", "cancelled by user");
753 | return false;
754 | }
755 | // user did select "add tasks"
756 | let input = p.fieldValues["tasks"];
757 | if (input.length == 0) {
758 | Draftist_cancelAction("Add Tasks from Prompt", "No input provided")
759 | return false;
760 | } else {
761 | let taskCreated = Draftist_createTasksFromLinesWithIdenticalSettings(input);
762 | if (taskCreated) {
763 | // succeeded
764 | Draftist_succeedAction("", false, "successfully added task");
765 | return true;
766 | } else {
767 | return false
768 | }
769 | }
770 | }
771 |
772 |
773 | /**
774 | * Draftist_createTasksFromLinesWithIndividualSettings - creates a task from each line in the passed text with individual settings for each line from prompts
775 | *
776 | * @param {String} text the string containing the tasks seperated by new lines
777 | * @return {Boolean} true if added successfully; false if adding tasks failed
778 | */
779 | function Draftist_createTasksFromLinesWithIndividualSettings(text) {
780 | if (text.length == 0) {
781 | return false;
782 | } else {
783 | let taskCount = 0;
784 | let lines = text.split("\n");
785 | for (line of lines) {
786 | if (line.length != 0) {
787 | if (Draftist_createTask(Draftist_createTaskObjectWithSettingsFromPrompt(line))) {
788 | // increase task counter
789 | taskCount = taskCount + 1;
790 | } else {
791 | // stop adding tasks and return immideately
792 | return false;
793 | }
794 | }
795 | }
796 | // succeeded
797 | Draftist_succeedAction("", false, "successfully added " + taskCount + " task(s)");
798 | }
799 | }
800 |
801 | /**
802 | * Draftist_createTasksFromLinesInDraftWithIndividualSettings - creates tasks for each line in the current draft with individual settings from prompts
803 | *
804 | * @return {Boolean} true if added successfully; false if adding tasks failed
805 | */
806 | function Draftist_createTasksFromLinesInDraftWithIndividualSettings() {
807 | if (draft.content.length != 0) {
808 | return Draftist_createTasksFromLinesWithIndividualSettings(draft.content);
809 | } else {
810 | Draftist_cancelAction("Tasks from lines in current Draft with individual settings", "Draft is blank")
811 | return false;
812 | }
813 | }
814 |
815 | /**
816 | * Draftist_createTasksFromLinesInPromptWithIndividualSettings - creates tasks for each line in the displayed prompt with individual settings from prompts
817 | *
818 | * @return {Boolean} true when added successfully, false when adding task failed
819 | */
820 | function Draftist_createTasksFromLinesInPromptWithIndividualSettings() {
821 | let p = new Prompt();
822 | p.title = "add tasks with individual settings";
823 | p.message = "each line will be its own task - each uses different settings from the next prompts"
824 | p.addTextView("tasks", "", "", {
825 | wantsFocus: true
826 | });
827 | p.addButton("add tasks");
828 |
829 | if (!p.show()) {
830 | Draftist_cancelAction("Add Tasks from Prompt", "cancelled by user");
831 | return false;
832 | }
833 | // user did select "add tasks"
834 | let input = p.fieldValues["tasks"];
835 | if (input.length == 0) {
836 | Draftist_cancelAction("Add Tasks from Prompt", "No input provided")
837 | return false;
838 | } else {
839 | let taskCreated = Draftist_createTasksFromLinesWithIndividualSettings(input);
840 | if (taskCreated) {
841 | // succeeded
842 | Draftist_succeedAction("", false, "successfully added task");
843 | return true;
844 | } else {
845 | return false
846 | }
847 | }
848 | }
849 |
850 |
851 | /**
852 | * Draftist_helperGetMdTasksFromCurrentDraft - searches the current draft for markdown tasks "- [ ]" and retruns an array with the "content" of the tasks
853 | *
854 | * @return {String[]} array of task contents
855 | */
856 | function Draftist_helperGetMdTasksFromCurrentDraft() {
857 | let content = draft.content;
858 | // find all lines with a task marker at the beginning which are uncompleted
859 | const regex = /^- \[\s\]\s(.*)$/gm;
860 | const subst = `$1`;
861 | let matches = [...content.matchAll(regex)]
862 | // the first group of each match is the task content (without the md task marker)
863 | let taskContents = matches.map(match => match[1])
864 | return taskContents
865 | }
866 |
867 |
868 | /**
869 | * Draftist_quickAddTasksFromMdTodoLinesInDraft - creates tasks for each md task in the current open draft. This function uses the Todoist quickAdd API which allows adding due dates, labels, projects with the syntax also used in the Todoist task input.
870 | *
871 | * @return {Boolean} true if added successfully (or no task was found), false if adding task failed
872 | */
873 | function Draftist_quickAddTasksFromMdTodoLinesInDraft() {
874 | let tasks = Draftist_helperGetMdTasksFromCurrentDraft()
875 | if (tasks.length > 0) {
876 | // combine task contents by new lines to work with the quickAddLines function
877 | let taskNumber = Draftist_quickAddLines(tasks.join("\n"));
878 | if (taskNumber) {
879 | // succeeded
880 | Draftist_succeedAction("", false, "successfully added " + taskNumber + " tasks :)")
881 | return true;
882 | } else {
883 | return false;
884 | }
885 | } else {
886 | Draftist_infoMessage("", "no (uncompleted) tasks found in draft");
887 | return true;
888 | }
889 | }
890 |
891 |
892 | /**
893 | * Draftist_createTasksWithIdenticalSettingsFromMdTasksInCurrentDraft - creates tasks with same settings for every md task in the current document. The settings can be selected in the displayed prompts.
894 | *
895 | * @return {Boolean} true if added successfully (or no task was found), false if adding task failed
896 | */
897 | function Draftist_createTasksWithIdenticalSettingsFromMdTasksInCurrentDraft() {
898 | let tasks = Draftist_helperGetMdTasksFromCurrentDraft()
899 | if (tasks.length > 0) {
900 | // combine task contents by new lines to input a text which is split in the called function
901 | return Draftist_createTasksFromLinesWithIdenticalSettings(tasks.join("\n"));
902 | } else {
903 | Draftist_infoMessage("", "no (uncompleted) tasks found in draft");
904 | return true;
905 | }
906 | }
907 |
908 | /**
909 | * Draftist_createTasksWithIndividualSettingsFromMdTasksInCurrentDraft - creates tasks with individual settings for every md task in the current document. The settings can be selected in the displayed prompts.
910 | *
911 | * @return {Boolean} true if added successfully (or no task was found), false if adding task failed
912 | */
913 | function Draftist_createTasksWithIndividualSettingsFromMdTasksInCurrentDraft() {
914 | let tasks = Draftist_helperGetMdTasksFromCurrentDraft()
915 | if (tasks.length > 0) {
916 | // combine task contents by new lines to input a text which is split in the called function
917 | return Draftist_createTasksFromLinesWithIndividualSettings(tasks.join("\n"));
918 | } else {
919 | Draftist_infoMessage("", "no (uncompleted) tasks found in draft");
920 | return true;
921 | }
922 | }
923 |
924 | // #############################################################################
925 | // IMPORT TASKS
926 | // #############################################################################
927 |
928 | /**
929 | * Draftist_createStringFromTasks - converts the passed tasks to strings with contents using the active settings for task strings
930 | *
931 | * @param {Tasks[]} tasks - Todoist task objects to convert to strings
932 | * @return {String} string containing the task informations
933 | */
934 | function Draftist_createStringFromTasks({
935 | tasks
936 | }) {
937 | Draftist_loadCurrentConfigurationSettings();
938 | const contentSettings = activeSettings["taskImportContents"];
939 | let tasksString = ""
940 | for (task of tasks) {
941 | // task content
942 | tasksString = tasksString + "- [ ] " + task.content
943 | // app link
944 | if (contentSettings.includes("appLink")) {
945 | tasksString = tasksString + " [📱](todoist://task?id=" + task.id + ")";
946 | }
947 | // web link
948 | if (contentSettings.includes("webLink")) {
949 | tasksString = tasksString + " [🌐](" + task.url + ")";
950 | }
951 |
952 | if (contentSettings.includes("projectName")) {
953 | if (projectsIdToNameMap.size == 0) {
954 | Draftist_getStoredTodoistData();
955 | }
956 | tasksString = tasksString + " *" + projectsIdToNameMap.get(task.project_id) + "*"
957 | }
958 |
959 | if (contentSettings.includes("priority")) {
960 | tasksString = tasksString + " p" + (5 - task.priority);
961 | }
962 |
963 | if (contentSettings.includes("labels")) {
964 | if (labelsIdToNameMap.size == 0) {
965 | Draftist_getStoredTodoistData();
966 | }
967 | for (label of task.labels) {
968 | tasksString = tasksString + " @" + label
969 | }
970 | }
971 | tasksString = tasksString + "\n"
972 | }
973 | return tasksString;
974 | }
975 |
976 |
977 | /**
978 | * Draftist_getTodoistTasksFromFilter - returns the tasks in todoist for a given filter string
979 | *
980 | * @param {String} filterString a valid todoist filter string
981 | * @return {Task[]} array of Task objects (JSON) for the given filter string
982 | */
983 | function Draftist_getTodoistTasksFromFilter(filterString) {
984 | let todoist = new Todoist()
985 | let tasks = todoist.getTasks({
986 | filter: filterString
987 | })
988 | //alert("lastResponse: " + JSON.stringify(todoist.lastResponse) + "\n\nlastError: " + todoist.lastError)
989 | const occuredError = Draftist_getLastTodoistError(todoist)
990 | if (occuredError) {
991 | //error occured
992 | Draftist_failAction("get tasks from filter \"" + filterString + "\"", occuredError)
993 | return false;
994 | }
995 | return tasks;
996 | }
997 |
998 |
999 | /**
1000 | * Draftist_importTodaysTasksIntoDraft - appends the tasks due today (or overdue) to the current draft
1001 | *
1002 | */
1003 | function Draftist_importTodaysTasksIntoDraft() {
1004 | const tasks = Draftist_getTodoistTasksFromFilter("overdue | today");
1005 | const stringToInsert = Draftist_createStringFromTasks({
1006 | tasks: tasks
1007 | })
1008 | draft.content = draft.content + "\n **TODAYs TASKs:**\n\n" + stringToInsert;
1009 | draft.update()
1010 | }
1011 |
1012 |
1013 | /**
1014 | * Draftist_importTasksFromProjectName - imports the tasks from the provided project name into the current draft
1015 | *
1016 | * @param {String} projectName the project name for the tasks to import
1017 | */
1018 | function Draftist_importTasksFromProjectName(projectName) {
1019 | if (projectsNameToIdMap.size == 0) {
1020 | Draftist_getStoredTodoistData();
1021 | }
1022 | const projectNames = Array.from(projectsNameToIdMap.keys());
1023 | // check if project name is available, if not fail the action
1024 | if (!projectNames.includes(projectName)) {
1025 | Draftist_failAction("import tasks from project", "project with name \"" + projectName + "\" is not existing in your Todoist Account")
1026 | return
1027 | }
1028 | const tasks = Draftist_getTodoistTasksFromFilter("##" + projectName)
1029 | const stringToInsert = Draftist_createStringFromTasks({
1030 | tasks: tasks
1031 | })
1032 | draft.content = draft.content + "\n **TASKs from " + projectName + ":**\n\n" + stringToInsert;
1033 | draft.update()
1034 | }
1035 |
1036 |
1037 | /**
1038 | * Draftist_importTasksFromSelectedProject - presents a prompt to let the user select a project and then imports the task of the selected project into the current draft
1039 | *
1040 | */
1041 | function Draftist_importTasksFromSelectedProject() {
1042 | if (projectsNameToIdMap.size == 0) {
1043 | Draftist_getStoredTodoistData();
1044 | }
1045 | let pProject = new Prompt();
1046 | pProject.title = "select the project"
1047 | let sortedProjectNameMap = new Map([...projectsNameToIdMap].sort((a, b) => String(a[0]).localeCompare(b[0])))
1048 | for (const [pName, pId] of sortedProjectNameMap) {
1049 | // selected button will directly contain the projects id as value
1050 | pProject.addButton(pName);
1051 | }
1052 | if (pProject.show()) {
1053 | Draftist_importTasksFromProjectName(pProject.buttonPressed);
1054 | } else {
1055 | Draftist_cancelAction("import tasks from project", "user cancelled")
1056 | }
1057 |
1058 | }
1059 |
1060 | /**
1061 | * Draftist_importTasksWithLabels - imports the tasks with the provided label names into the current draft. Depending on the input parameter either all or any given labels must be included in a task.
1062 | *
1063 | * @param {String} labelNames the label names for the tasks to import (separated by a comma)
1064 | * @param {Boolean} requireAllLabels if set to true all given labels must be present in the task to be imported, if set to false only one of the given labels must be present in the task
1065 | */
1066 |
1067 | function Draftist_importTasksWithLabels(labelNames, requireAllLabels) {
1068 | const requestedLabels = labelNames.split(",");
1069 | if (labelsNameToIdMap.size == 0) {
1070 | Draftist_getStoredTodoistData();
1071 | }
1072 | const validLabelNames = Array.from(labelsNameToIdMap.keys());
1073 | let labelStrings = [];
1074 | for (labelName of requestedLabels) {
1075 | // check if all given label names are available, if not fail the action
1076 | if (!validLabelNames.includes(labelName)) {
1077 | Draftist_failAction("import tasks with label", "label with name \"" + labelName + "\" is not existing in your Todoist Account")
1078 | return
1079 | }
1080 | labelStrings.push("@" + labelName);
1081 | }
1082 | const filterString = labelStrings.join((requireAllLabels ? " & " : " | "))
1083 | const tasks = Draftist_getTodoistTasksFromFilter(filterString)
1084 | const stringToInsert = Draftist_createStringFromTasks({
1085 | tasks: tasks
1086 | })
1087 | draft.content = draft.content + "\n **TASKs with label(s) " + filterString + ":**\n\n" + stringToInsert;
1088 | draft.update()
1089 | }
1090 |
1091 |
1092 | /**
1093 | * Draftist_importTasksWithSelectedLabels - imports the tasks from the selected labels in a prompt into the current draft. Depending on the input parameter either all or any given labels must be included in a task.
1094 | *
1095 | * @param {type} requireAllLabels if set to true all given labels must be present in the task to be imported, if set to false only one of the given labels must be present in the task
1096 | */
1097 | function Draftist_importTasksWithSelectedLabels(requireAllLabels) {
1098 | if (labelsNameToIdMap.size == 0) {
1099 | Draftist_getStoredTodoistData();
1100 | }
1101 | let pLabels = new Prompt();
1102 | pLabels.title = "select the labels"
1103 | pLabels.message = (requireAllLabels ? "all selected labels " : "any selected label") + " must be present in the task"
1104 | let sortedLabelNameMap = new Map([...labelsNameToIdMap].sort((a, b) => String(a[0]).localeCompare(b[0])))
1105 | pLabels.addSelect("selectedLabels", "", Array.from(sortedLabelNameMap.keys()), [], true)
1106 | pLabels.addButton("Apply")
1107 | if (pLabels.show()) {
1108 | Draftist_importTasksWithLabels(pLabels.fieldValues["selectedLabels"].join(","), requireAllLabels);
1109 | } else {
1110 | Draftist_cancelAction("import tasks from project", "user cancelled")
1111 | }
1112 | }
1113 |
1114 |
1115 | /**
1116 | * Draftist_importTasksFromFilter - imports the tasks for the given filter string into the current draft
1117 | *
1118 | * @param {type} filterString filter string for Todoist tasks
1119 | */
1120 | function Draftist_importTasksFromFilter(filterString) {
1121 | const tasks = Draftist_getTodoistTasksFromFilter(filterString);
1122 | if (tasks) {
1123 | const stringToInsert = Draftist_createStringFromTasks({
1124 | tasks: tasks
1125 | })
1126 | draft.content = draft.content + "\n **TASKs from filter \"" + filterString + "\":**\n\n" + stringToInsert;
1127 | draft.update()
1128 | }
1129 | }
1130 |
1131 |
1132 | /**
1133 | * Draftist_importTasksFromFilterInPrompt - imports the tasks from the filter typed into the text field in the prompt
1134 | *
1135 | */
1136 | function Draftist_importTasksFromFilterInPrompt() {
1137 | let pFilter = new Prompt()
1138 | pFilter.title = "set the filter query";
1139 | pFilter.message = "you can use any supported filter query by Todoist"
1140 | pFilter.addTextField("filterString", "", "", {
1141 | wantsFocus: true
1142 | });
1143 | pFilter.addButton("Apply");
1144 | if (pFilter.show()) {
1145 | Draftist_importTasksFromFilter(pFilter.fieldValues["filterString"]);
1146 | } else {
1147 | Draftist_cancelAction("import tasks from filter", "user cancelled")
1148 | }
1149 | }
1150 |
1151 | // #############################################################################
1152 | // MODIFY TASKS
1153 | // #############################################################################
1154 |
1155 | /**
1156 | * Draftist_updateTask updates the provided task with the provided options
1157 | *
1158 | * @param {Todoist_Object} todoist_obj? - if already created, otherwise the function will create its own.
1159 | * @param {Todoist_Task} taskToUpdate - the task that should be updated
1160 | * @param {String[]} labelNamesToRemove? - the valid label names which should be removed from the provided task
1161 | * @param {String[]} labelNamesToAdd? - the valid label names which should be added to the provided task
1162 | * @param {String} newDueDateString? - the new due date provided as String (best in the format YYYY-MM-DD; but Human defined dates are possible (https://developer.todoist.com/rest/v1/#update-a-task // https://todoist.com/help/articles/due-dates-and-times)
1163 | * @param {String} newProjectName? - ATTENTION: currently 05/2022 not supported by the todoist API, providing this parameter will fail the action - the new valid project name for the provided task
1164 | * @returns {Boolean} true when updated successfully, false when updating failed or any parameter was not valid (e.g. label name is not existing)
1165 | */
1166 | function Draftist_updateTask({
1167 | todoist = new Todoist(),
1168 | taskToUpdate,
1169 | labelNamesToRemove = [],
1170 | labelNamesToAdd = [],
1171 | newDueDateString = undefined,
1172 | newProjectName = undefined
1173 | }) {
1174 | if (!taskToUpdate) {
1175 | Draftist_failAction("update task", "no task to update was provided")
1176 | return false
1177 | }
1178 | const taskId = taskToUpdate.id;
1179 |
1180 | // update labels
1181 | const currentLabels = taskToUpdate.labels;
1182 | // init projectId variable
1183 |
1184 | // load todoist data if not already loaded and a relevant parameter is present & contains relevant values (e.g. labels, project id)
1185 | if (labelsNameToIdMap.size == 0 && (labelNamesToRemove.length > 0 || labelNamesToAdd.length > 0 || newProjectName)) {
1186 | Draftist_getStoredTodoistData();
1187 | }
1188 |
1189 | // init labelId arrays
1190 | let labelIdsToRemove = [];
1191 | let labelIdsToAdd = [];
1192 |
1193 | for (labelName of labelNamesToRemove) {
1194 | const curLabelId = labelsNameToIdMap.get(labelName);
1195 | if (!curLabelId) {
1196 | Draftist_failAction("update task", "provided label name \"" + labelName + "\" is not existing.");
1197 | return false
1198 | }
1199 | labelIdsToRemove.push(curLabelId);
1200 | }
1201 |
1202 | for (labelName of labelNamesToAdd) {
1203 | const curLabelId = labelsNameToIdMap.get(labelName);
1204 | if (!curLabelId) {
1205 | Draftist_failAction("update task", "provided label name \"" + labelName + "\" is not existing.");
1206 | return false
1207 | }
1208 | labelIdsToAdd.push(curLabelId);
1209 | }
1210 |
1211 |
1212 |
1213 | let updatedLabelIds = labelIdsToAdd;
1214 | let updatedLabels = labelNamesToAdd;
1215 |
1216 | for (curLabel of currentLabels) {
1217 | // add the label to the updated array if its not already included and it is not contained in the labelsToRemove Array
1218 | if (!labelIdsToRemove.includes(curLabel) && !updatedLabelIds.includes(curLabel)) {
1219 | updatedLabelIds.push(curLabel);
1220 | }
1221 | if (!labelNamesToRemove.includes(curLabel) && !updatedLabels.includes(curLabel)) {
1222 | updatedLabels.push(curLabel);
1223 | }
1224 |
1225 | }
1226 |
1227 | // update due date / date time
1228 |
1229 | let dueDateString = taskToUpdate.due_date;
1230 | if (newDueDateString) {
1231 | // new due date was provided
1232 | dueDateString = newDueDateString
1233 | }
1234 |
1235 | // attention the project ID was implemented based on the task property.
1236 | // turned out that project_id is not a parameter for the update task request: https://developer.todoist.com/rest/v1/#update-a-task
1237 | // support request was sent on 2022-05-12 but until implementation this is a point of failure and will fail the function for now.
1238 | let projectId = taskToUpdate.project_id;
1239 | if (newProjectName) {
1240 | // fail the action until project id is supported by Todoist:
1241 | Draftist_failAction("update task", "new project name was provided but is currently not supported by Todoist")
1242 | return false;
1243 | projectId = projectsNameToIdMap.get(newProjectName);
1244 | if (!projectId) {
1245 | Draftist_failAction("update task", "provided project name \"" + newProjectName + "\" is not existing.");
1246 | return false
1247 | }
1248 | }
1249 |
1250 | // create task options
1251 | let options = {
1252 | "content": taskToUpdate.content,
1253 | "description": taskToUpdate.description,
1254 | "project_id": projectId,
1255 | "section_id": taskToUpdate.section_id,
1256 | "parent_id": taskToUpdate.parent_id,
1257 | "order": taskToUpdate.order,
1258 | "labels": updatedLabels,
1259 | "priority": taskToUpdate.priority,
1260 | "due_string": (dueDateString ? dueDateString : undefined),
1261 | "assignee": taskToUpdate.assignee
1262 | };
1263 |
1264 | const updateTaskResult = todoist.updateTask(taskId, options);
1265 | if (!updateTaskResult) {
1266 | const lastError = Draftist_getLastTodoistError(todoist);
1267 | if (lastError) {
1268 | Draftist_failAction("update task", "todoist returned error: " + lastError)
1269 | } else {
1270 | Draftist_failAction("update task", "unknown error occured. please try again and contact @FlohGro with steps to reproduce")
1271 | }
1272 | return false;
1273 | } else {
1274 | return true;
1275 | }
1276 |
1277 | }
1278 |
1279 | /**
1280 | * Draftist_updateLabelsOfTask updates the labels of the provided task with the provided options
1281 | *
1282 | * @param {Todoist_Object} todoist_obj? - if already created, otherwise the function will create its own.
1283 | * @param {Todoist_Task} taskToUpdate - the task that should be updated
1284 | * @param {String[]} labelNamesToRemove? - the valid label names which should be removed from the provided task
1285 | * @param {String[]} labelNamesToAdd? - the valid label names which should be added to the provided task
1286 | * @returns {Boolean} true when updated successfully, false when updating failed or any parameter was not valid (e.g. label name is not existing)
1287 | */
1288 | function Draftist_updateLabelsOfTask({
1289 | todoist = new Todoist(),
1290 | taskToUpdate,
1291 | labelNamesToRemove,
1292 | labelNamesToAdd
1293 | }) {
1294 | return Draftist_updateTask({
1295 | todoist: todoist,
1296 | taskToUpdate: taskToUpdate,
1297 | labelNamesToRemove: labelNamesToRemove,
1298 | labelNamesToAdd: labelNamesToAdd
1299 | })
1300 | }
1301 |
1302 | /**
1303 | * Draftist_updateProjectOfTask updates the project of the provided task to the provided project name (not supported by Todoist right now)
1304 | *
1305 | * @param {Todoist_Object} todoist_obj? - if already created, otherwise the function will create its own.
1306 | * @param {Todoist_Task} taskToUpdate - the task that should be updated
1307 | * @param {String} newProjectName - the valid new project name for the task
1308 | * @returns {Boolean} true when updated successfully, false when updating failed or any parameter was not valid (e.g. project name is not existing)
1309 | */
1310 | function Draftist_updateProjectOfTask({
1311 | todoist = new Todoist(),
1312 | taskToUpdate,
1313 | newProjectName
1314 | }) {
1315 | return Draftist_updateTask({
1316 | todoist: todoist,
1317 | taskToUpdate: taskToUpdate,
1318 | newProjectName: newProjectName
1319 | })
1320 | }
1321 |
1322 |
1323 | /**
1324 | * Draftist_updateDueDateOfTask updates the due date of the provided task to the provided new date
1325 | *
1326 | * @param {Todoist_Object} todoist_obj? - if already created, otherwise the function will create its own.
1327 | * @param {Todoist_Task} taskToUpdate - the task that should be updated
1328 | * @param {String} newDueDateString - the new due date provided as String (best in the format YYYY-MM-DD; but Human defined dates are possible (https://developer.todoist.com/rest/v1/#update-a-task // https://todoist.com/help/articles/due-dates-and-times)
1329 | * @returns {Boolean} true when updated successfully, false when updating failed or any parameter was not valid (e.g. unsupported date format)
1330 | */
1331 | function Draftist_updateDueDateOfTask({
1332 | todoist = new Todoist(),
1333 | taskToUpdate,
1334 | newDueDateString
1335 | }) {
1336 | return Draftist_updateTask({
1337 | todoist: todoist,
1338 | taskToUpdate: taskToUpdate,
1339 | newDueDateString: newDueDateString
1340 | })
1341 | }
1342 |
1343 |
1344 | /**
1345 | * Draftist_selectTasksFromTaskObjects - displays a prompt to let the user select one or multiple tasks and returns the selected ones
1346 | *
1347 | * @param {Todoist_Task[]} taskObjects - array of todoist task objects
1348 | * @param {Boolean} allowSelectMultiple - parameter to define if selecting multiple tasks shall be allowed (true) or not (false)
1349 | * @param {String} promptMessage? - a descriptive message which should be displayed inside the prompt
1350 | * @returns {Todoist_Task[]} - Array of selected Todoist Task (might be empty)
1351 | */
1352 | function Draftist_selectTasksFromTaskObjects(taskObjects, allowSelectMultiple, promptMessage = "") {
1353 | let selectedTasks = [];
1354 | if (taskObjects.length == 0) {
1355 | // return empty array immediately
1356 | return [];
1357 | }
1358 | let pTasks = new Prompt();
1359 | pTasks.title = "select tasks";
1360 | if (promptMessage != "") {
1361 | pTasks.message = promptMessage;
1362 | }
1363 | pTasks.addSelect("selectedTasks", "", taskObjects.map(task => task.content), [], allowSelectMultiple)
1364 | pTasks.addButton("select")
1365 | if (pTasks.show()) {
1366 | const selectedTaskContents = pTasks.fieldValues["selectedTasks"];
1367 | // iterate through the selected task contents
1368 | for (taskContent of selectedTaskContents) {
1369 | // add the task Object with the selected content to the array
1370 | selectedTasks = selectedTasks.concat(taskObjects.filter(task => (task.content == taskContent)))
1371 | }
1372 | } else {
1373 | Draftist_cancelAction("select tasks", "user aborted")
1374 | }
1375 | return selectedTasks;
1376 | }
1377 |
1378 | /**
1379 | * Draftist_helperGetAnyPresentLabelNamesInTasks - gets the names of labels present in at least one of the provided tasks
1380 | * @param {Todoist_Task[]} taskObjects - array of todoist tasks
1381 | * @returns {String[]} Array of present label names in at least one of the provided tasks
1382 | */
1383 | function Draftist_helperGetAnyPresentLabelNamesInTasks(taskObjects) {
1384 | // prevent empty taskObjects and return empty array in that case
1385 | if (taskObjects.length == 0) {
1386 | return [];
1387 | }
1388 | // use a Set to prevent duplicates
1389 | let labelNames = new Set();
1390 |
1391 | // load stored data if not laoded already
1392 | if (labelsIdToNameMap.size == 0) {
1393 | Draftist_getStoredTodoistData();
1394 | }
1395 |
1396 | for (task of taskObjects) {
1397 | // add each label id to the set
1398 | for (labelName of task.labels) {
1399 | labelNames.add(labelName)
1400 | }
1401 | }
1402 | // convert to array to return it
1403 | return Array.from(labelNames.values())
1404 | }
1405 |
1406 | /**
1407 | * Draftist_helperGetCommonPresentLabelNamesInTasks - gets the names of labels present in all of the provided tasks
1408 | * @param {Todoist_Task[]} taskObjects - array of todoist tasks
1409 | * @returns {String[]} Array of present label names present in all of the provided tasks
1410 | */
1411 | function Draftist_helperGetCommonPresentLabelNamesInTasks(taskObjects) {
1412 | // prevent empty taskObjects and return empty array in that case
1413 | if (taskObjects.length == 0) {
1414 | return [];
1415 | }
1416 |
1417 | // load stored data if not laoded already
1418 | if (labelsIdToNameMap.size == 0) {
1419 | Draftist_getStoredTodoistData();
1420 | }
1421 |
1422 | // logic:
1423 | // 1) start with the first task and store its label names
1424 | // 2) if no labels are present, immideately return an empty array
1425 | // 3) repeat with each task: check if all current stored labels are present in it
1426 | // 3.1) if yes, continue
1427 | // 3.2) if not, remove the labels not present from the store and continue
1428 | // 4) get the names from all remaining labels and return as an array
1429 |
1430 | let presentLabelNames = taskObjects[0].labels;
1431 |
1432 | if (presentLabelNames.length == 0) {
1433 | return [];
1434 | }
1435 |
1436 | for (task of taskObjects) {
1437 | presentLabelNames = presentLabelNames.filter(x => task.labels.includes(x))
1438 | }
1439 |
1440 |
1441 | return presentLabelNames
1442 | }
1443 |
1444 | /**
1445 | * Draftist_helperGetLabelNamesNotPresentInAllTasks - gets the names of labels not present in all of the provided tasks
1446 | * @param {Todoist_Task[]} taskObjects - array of todoist tasks
1447 | * @returns {String[]} Array of present label names not present in all of the provided tasks
1448 | */
1449 | function Draftist_helperGetLabelNamesNotPresentInAllTasks(taskObjects) {
1450 | // get all labelNames
1451 | // load stored data if not laoded already
1452 | if (labelsNameToIdMap.size == 0) {
1453 | Draftist_getStoredTodoistData();
1454 | }
1455 | let labelNames = Array.from(labelsNameToIdMap.keys());
1456 | let commonLabels = Draftist_helperGetCommonPresentLabelNamesInTasks(taskObjects)
1457 |
1458 | return labelNames.filter(x => !commonLabels.includes(x))
1459 | }
1460 |
1461 |
1462 | /**
1463 | * Draftist_updateLabelsOfSelectedTasksFromFilter - updates the label(s) of the selected tasks returned for the provided filter
1464 | * @param {String} filterString - a valid todoist filter string
1465 | * @returns true if updated successfully, false if update failed or user cancelled
1466 | */
1467 | function Draftist_updateLabelsOfSelectedTasksFromFilter(filterString) {
1468 | let tasksFromFilter = Draftist_getTodoistTasksFromFilter(filterString)
1469 | // early retrun if no task was retrieved
1470 | if (!tasksFromFilter) {
1471 | return false;
1472 | }
1473 | // let the user select the tasks
1474 | let selectedTasks = Draftist_selectTasksFromTaskObjects(tasksFromFilter, true, "from filter \"" + filterString + "\"");
1475 | let availableLableNames = Draftist_helperGetAnyPresentLabelNamesInTasks(selectedTasks);
1476 | let potentialLabelNamesToAdd = Draftist_helperGetLabelNamesNotPresentInAllTasks(selectedTasks);
1477 |
1478 | if (selectedTasks.length == 0) {
1479 | return false;
1480 | }
1481 | // load stored data if not laoded already
1482 | if (labelsNameToIdMap.size == 0) {
1483 | Draftist_getStoredTodoistData();
1484 | }
1485 |
1486 | // declare before if condition
1487 | let labelNamesToRemove = []
1488 | // only present remove menu if any label is present
1489 | if (availableLableNames.length > 0) {
1490 | let pLabelsToRemove = new Prompt()
1491 | pLabelsToRemove.title = "select labels to remove";
1492 | pLabelsToRemove.message = "all selected labels will be removed from the tasks (if they have the tags assigned). If you don't want to remove labels, just select no label and press \"select\""
1493 | pLabelsToRemove.addSelect("labelsToRemove", "", availableLableNames, [], true)
1494 | pLabelsToRemove.addButton("select")
1495 | if (pLabelsToRemove.show()) {
1496 | labelNamesToRemove = pLabelsToRemove.fieldValues["labelsToRemove"]
1497 | } else {
1498 | Draftist_cancelAction("update labels", "user cancelled")
1499 | return false;
1500 | }
1501 | }
1502 |
1503 | let labelNamesToAdd = []
1504 | let pLabelsToAdd = new Prompt()
1505 | pLabelsToAdd.title = "select labels to add";
1506 | pLabelsToAdd.message = "all selected labels will be added to the selected tasks. If you don't want to add labels, just select no label and press \"select\""
1507 | pLabelsToAdd.addSelect("labelsToAdd", "", potentialLabelNamesToAdd, [], true)
1508 | pLabelsToAdd.addButton("select")
1509 | if (pLabelsToAdd.show()) {
1510 | labelNamesToAdd = pLabelsToAdd.fieldValues["labelsToAdd"]
1511 | } else {
1512 | Draftist_cancelAction("update labels", "user cancelled")
1513 | return false;
1514 | }
1515 |
1516 | // create todoist object to use
1517 | let todoistObj = new Todoist()
1518 | let updatedTasksCount = 0;
1519 | // iterate through all selected tasks and update them
1520 | for (task of selectedTasks) {
1521 | if (!Draftist_updateLabelsOfTask({
1522 | todoist: todoistObj,
1523 | taskToUpdate: task,
1524 | labelNamesToRemove: labelNamesToRemove,
1525 | labelNamesToAdd: labelNamesToAdd
1526 | })) {
1527 | // failed updating failure is already presented, just exit the function here
1528 | return false;
1529 | } else {
1530 | updatedTasksCount = updatedTasksCount + 1;
1531 | }
1532 | }
1533 |
1534 | Draftist_succeedAction("update labels", false, "updated labels of " + updatedTasksCount + " tasks")
1535 | return true;
1536 |
1537 | }
1538 |
1539 |
1540 | /**
1541 | * Draftist_updateProjectOfSelectedTasksFromFilter - ATTENTION: this is currently not supported by Todoists API - updates the project of the selected tasks returned for the provided filter
1542 | * @param {String} filterString - a valid todoist filter string
1543 | * @returns true if updated successfully, false if update failed or user cancelled
1544 | */
1545 | function Draftist_updateProjectOfSelectedTasksFromFilter(filterString) {
1546 | // fail the action until project id is supported by Todoist:
1547 | Draftist_failAction("update project of tasks", "this is currently not supported by Todoist")
1548 | return false;
1549 | let tasksFromFilter = Draftist_getTodoistTasksFromFilter(filterString)
1550 | // early retrun if no task was retrieved
1551 | if (!tasksFromFilter) {
1552 | return false;
1553 | }
1554 | // let the user select the tasks
1555 | let selectedTasks = Draftist_selectTasksFromTaskObjects(tasksFromFilter, true, "from filter \"" + filterString + "\"");
1556 | if (selectedTasks.length == 0) {
1557 | return false;
1558 | }
1559 |
1560 | // load stored data if not laoded already
1561 | if (projectsNameToIdMap.size == 0) {
1562 | Draftist_getStoredTodoistData();
1563 | }
1564 | let pProject = new Prompt();
1565 | pProject.title = "select new project"
1566 | // add a button to the prompt for each available project name
1567 | Array.from(projectsNameToIdMap.keys()).map((x) => pProject.addButton(x));
1568 |
1569 | if (!pProject.show()) {
1570 | // user did not select a project
1571 | }
1572 |
1573 | const selectedProject = pProject.buttonPressed;
1574 |
1575 | // create todoist object to use
1576 | let todoistObj = new Todoist()
1577 | let updatedTasksCount = 0;
1578 | // iterate through all selected tasks and update them
1579 | for (task of selectedTasks) {
1580 | if (!Draftist_updateProjectOfTask({
1581 | todoistObj,
1582 | taskToUpdate: task,
1583 | newProjectName: selectedProject
1584 | })) {
1585 | // failed updating failure is already presented, just exit the function here
1586 | return false;
1587 | } else {
1588 | updatedTasksCount = updatedTasksCount + 1;
1589 | }
1590 | }
1591 |
1592 | Draftist_succeedAction("update project", false, "updated project of " + updatedTasksCount + " tasks")
1593 | return true;
1594 | }
1595 |
1596 | /**
1597 | * Draftist_duplicateSelectedTasksFromLabelWithOtherLabel - duplicates each selected task with a source label. the duplicated tasks will not contain the source label anymore but will contain the destination label
1598 | *
1599 | * @param {Todoist_Object} todoistObj? - the todoist object to use
1600 | * @param {String} sourceLabelName - the name of the source label (must be a valid name of a label in the users todoist account) with or without the @ sign
1601 | * @param {String} destinationLabelName - the name of the destination label (must be a valid name of a label in the users todoist account) with or without the @ sign
1602 | * @returns true if duplicating all selected tasks succeeded, false if it failed (will not proceed if one task fails)
1603 | */
1604 | function Draftist_duplicateSelectedTasksFromLabelWithOtherLabel({
1605 | todoistObj = new Todoist(),
1606 | sourceLabelName,
1607 | destinationLabelName
1608 | }) {
1609 | // remove "@" sign from labelNames if they are present.
1610 | sourceLabelName = sourceLabelName.replace(/@(.*)/gm, `$1`);
1611 | destinationLabelName = destinationLabelName.replace(/@(.*)/gm, `$1`);
1612 |
1613 | // load stored data if not laoded already
1614 | if (labelsNameToIdMap.size == 0) {
1615 | Draftist_getStoredTodoistData();
1616 | }
1617 | //get label Ids for source and destination label
1618 | const sourceLabelId = labelsNameToIdMap.get(sourceLabelName);
1619 | const destinationLabelId = labelsNameToIdMap.get(destinationLabelName);
1620 | if (!sourceLabelId) {
1621 | // source label id is not existing
1622 | Draftist_failAction("duplicate task with different label", "source label \"" + sourceLabelName + "\" not found");
1623 | return false;
1624 | }
1625 | if (!destinationLabelId) {
1626 | // destination label id is not existing
1627 | Draftist_failAction("duplicate task with different label", "destination label \"" + destinationLabelName + "\" not found");
1628 | return false;
1629 | }
1630 |
1631 | // retrieve all tasks with the given source label name and let the user select the tasks to duplicate
1632 | const sourceTasks = Draftist_getTodoistTasksFromFilter("@" + sourceLabelName);
1633 | const selectedTasks = Draftist_selectTasksFromTaskObjects(sourceTasks, true, "duplicate tasks from @" + sourceLabelName + " to @" + destinationLabelName);
1634 | if (selectedTasks.length == 0) {
1635 | Draftist_cancelAction("", "user cancelled / did not select any task")
1636 | return true;
1637 | }
1638 | let createdTasksCount = 0;
1639 | for (task of selectedTasks) {
1640 | // create a new task object, remove the source label and add the destination label
1641 | let curNewTask = task;
1642 |
1643 | if (task.due) {
1644 | curNewTask.due_string = task.due.string;
1645 | }
1646 |
1647 | let labels = new Set(task.labels);
1648 | if (!labels.has(destinationLabelName)) {
1649 | labels.add(destinationLabelName)
1650 | }
1651 | labels.delete(sourceLabelName)
1652 | curNewTask.labels = Array.from(labels.values());
1653 | // delete section id if it is zero (Todoist will otherwise report an error)
1654 | if (curNewTask.section_id == 0) {
1655 | delete curNewTask["section_id"];
1656 | }
1657 | if (!todoistObj.createTask(curNewTask)) {
1658 | let lastError = Draftist_getLastTodoistError(todoistObj);
1659 | Draftist_failAction("duplicate task with different label", "Todoist returned error:\n" + lastError)
1660 | return false;
1661 | }
1662 | createdTasksCount = createdTasksCount + 1;
1663 | }
1664 | Draftist_succeedAction("duplicate task with different label", false, "created " + createdTasksCount + " tasks")
1665 | return true;
1666 | }
1667 |
1668 |
1669 | /**
1670 | * Draftist_changeLabelofSelectedTasksToOtherLabel - changes the given label of each selected task to the new label
1671 | *
1672 | * @param {Todoist_Object} todoistObj? - the todoist object to use
1673 | * @param {String} sourceLabelName - the name of the source label (must be a valid name of a label in the users todoist account) with or without the @ sign
1674 | * @param {String} destinationLabelName - the name of the destination label (must be a valid name of a label in the users todoist account) with or without the @ sign
1675 | * @returns true if updating all selected tasks succeeded, false if it failed (will not proceed if one task fails)
1676 | */
1677 | function Draftist_changeLabelofSelectedTasksToOtherLabel({
1678 | todoistObj = new Todoist(),
1679 | sourceLabelName,
1680 | destinationLabelName
1681 | }) {
1682 | // remove "@" sign from labelNames if they are present.
1683 | sourceLabelName = sourceLabelName.replace(/@(.*)/gm, `$1`);
1684 | destinationLabelName = destinationLabelName.replace(/@(.*)/gm, `$1`);
1685 |
1686 | // load stored data if not laoded already
1687 | if (labelsNameToIdMap.size == 0) {
1688 | Draftist_getStoredTodoistData();
1689 | }
1690 | //get label Ids for source and destination label
1691 | const sourceLabelId = labelsNameToIdMap.get(sourceLabelName);
1692 | const destinationLabelId = labelsNameToIdMap.get(destinationLabelName);
1693 | if (!sourceLabelId) {
1694 | // source label id is not existing
1695 | Draftist_failAction("change label of task", "source label \"" + sourceLabelName + "\" not found");
1696 | return false;
1697 | }
1698 | if (!destinationLabelId) {
1699 | // destination label id is not existing
1700 | Draftist_failAction("change label of task", "destination label \"" + destinationLabelName + "\" not found");
1701 | return false;
1702 | }
1703 |
1704 | // retrieve all tasks with the given source label name and let the user select the tasks to duplicate
1705 | const sourceTasks = Draftist_getTodoistTasksFromFilter("@" + sourceLabelName);
1706 | const selectedTasks = Draftist_selectTasksFromTaskObjects(sourceTasks, true, "duplicate tasks from @" + sourceLabelName + " to @" + destinationLabelName);
1707 | if (selectedTasks.length == 0) {
1708 | Draftist_cancelAction("", "user cancelled / did not select any task")
1709 | return true;
1710 | }
1711 | let updatedTasksCount = 0;
1712 | for (task of selectedTasks) {
1713 |
1714 | alert(sourceLabelName)
1715 | Draftist_updateLabelsOfTask({
1716 | todoist: todoistObj,
1717 | taskToUpdate: task,
1718 | labelNamesToRemove: [sourceLabelName],
1719 | labelNamesToAdd: [destinationLabelName]
1720 | })
1721 |
1722 | updatedTasksCount = updatedTasksCount + 1;
1723 | }
1724 | Draftist_succeedAction("change label of task", false, "created " + updatedTasksCount + " tasks")
1725 | return true;
1726 | }
1727 |
1728 | /**
1729 | * Draftist_helperGetNewDueDateFromPrompt - asks the user for a due date and creates an iso date string from the selected date
1730 | * @param {String} taskContent - the content of the task(s) to display in the prompt
1731 | * @returns selected due date as String in the format "YYYY-MM-DD"
1732 | */
1733 | function Draftist_helperGetNewDueDateFromPrompt(taskContent) {
1734 | // due date prompt
1735 | let pDate = new Prompt()
1736 | pDate.title = "select due date for task(s):";
1737 | pDate.message = taskContent;
1738 | pDate.addButton("today");
1739 | pDate.addButton("tomorrow");
1740 | pDate.addButton("other");
1741 | pDate.addButton("remove due date")
1742 | pDate.isCancellable = false;
1743 | pDate.show();
1744 | // if buttonPressed is undefined no due date was selected
1745 | const dateIsSet = (pDate.buttonPressed ? true : false);
1746 | let selectedDateString = undefined;
1747 | if (dateIsSet) {
1748 | if (pDate.buttonPressed == "other") {
1749 | let pSelDate = new Prompt();
1750 | pSelDate.title = "select custom date for \"" + taskContent + "\":";
1751 | let tomorrow = new Date(new Date().setDate(new Date().getDate() + 1));
1752 | pSelDate.addDatePicker("dueDatePicker", "", tomorrow, {
1753 | "mode": "date",
1754 | "minimumDate": tomorrow
1755 | });
1756 |
1757 | pSelDate.addButton("set due date");
1758 | pSelDate.isCancellable = false;
1759 | pSelDate.show();
1760 | let pickedDueDate = pSelDate.fieldValues["dueDatePicker"];
1761 | let day = pickedDueDate.getDate();
1762 | let month = pickedDueDate.getMonth() + 1;
1763 | let year = pickedDueDate.getFullYear();
1764 | selectedDateString = String(year) + "-" + String(month) + "-" + String(day);
1765 | } else {
1766 | let today = new Date()
1767 | switch (pDate.buttonPressed) {
1768 | case "today":
1769 | selectedDateString = today.getFullYear() + "-" + (today.getMonth() + 1) + "-" + today.getDate();
1770 | break;
1771 | //case "today": selectedDateString = today.toISOString(); break;
1772 | case "tomorrow":
1773 | let tomorrow = today.setDate(today.getDate() + 1);
1774 | selectedDateString = today.getFullYear() + "-" + (today.getMonth() + 1) + "-" + today.getDate();
1775 | break;
1776 | case "remove due date":
1777 | selectedDateString = "no date";
1778 | break;
1779 | }
1780 | }
1781 | }
1782 | return selectedDateString;
1783 | }
1784 |
1785 | /**
1786 | * Draftust_updateIndividualDueDateOfSelectedTasksFromFilter - updates the due date of each selected task from a filter to an individual selectable due date
1787 | *
1788 | * @param {String} filterString - a valid todoist filter string
1789 | * @returns true if all selected tasks updated successfully, false if something failed
1790 | */
1791 | function Draftust_updateIndividualDueDateOfSelectedTasksFromFilter(filterString) {
1792 | let tasksFromFilter = Draftist_getTodoistTasksFromFilter(filterString)
1793 | // early retrun if no task was retrieved
1794 | if (!tasksFromFilter) {
1795 | return false;
1796 | }
1797 | // let the user select the tasks
1798 | let selectedTasks = Draftist_selectTasksFromTaskObjects(tasksFromFilter, true, "from filter \"" + filterString + "\"");
1799 | if (selectedTasks.length == 0) {
1800 | return false;
1801 | }
1802 |
1803 | // iterate through all selected tasks
1804 | // ask for new due date
1805 | // update the task
1806 | let updatedTasksCount = 0;
1807 | let todoistObj = new Todoist();
1808 | for (task of selectedTasks) {
1809 | let newDueDateString = Draftist_helperGetNewDueDateFromPrompt(task.content)
1810 |
1811 | if (!Draftist_updateDueDateOfTask({
1812 | todoist: todoistObj,
1813 | taskToUpdate: task,
1814 | newDueDateString: newDueDateString
1815 | })) {
1816 | // not needed, update function will already display the error
1817 | //let lastError = Draftist_getLastTodoistError(todoistObj);
1818 | //Draftist_failAction("update due date", "Todoist returned error:\n" + lastError)
1819 | return false;
1820 | }
1821 | updatedTasksCount = updatedTasksCount + 1;
1822 | }
1823 | Draftist_succeedAction("update due date", false, "updated " + updatedTasksCount + " tasks")
1824 | return true;
1825 | }
1826 |
1827 | /**
1828 | * Draftust_updateDueDateToSameDateOfSelectedTasksFromFilter - updates the due date of each selected task from a filter to one common selectable due date
1829 | *
1830 | * @param {String} filterString - a valid todoist filter string
1831 | * @returns true if all selected tasks updated successfully, false if something failed
1832 | */
1833 | function Draftust_updateDueDateToSameDateOfSelectedTasksFromFilter(filterString) {
1834 | let tasksFromFilter = Draftist_getTodoistTasksFromFilter(filterString)
1835 | // early retrun if no task was retrieved
1836 | if (!tasksFromFilter) {
1837 | return false;
1838 | }
1839 | // let the user select the tasks
1840 | let selectedTasks = Draftist_selectTasksFromTaskObjects(tasksFromFilter, true, "from filter \"" + filterString + "\"");
1841 | if (selectedTasks.length == 0) {
1842 | return false;
1843 | }
1844 |
1845 | // iterate through all selected tasks
1846 | // ask for new due date
1847 | // update the task
1848 | let updatedTasksCount = 0;
1849 | let todoistObj = new Todoist();
1850 | let newDueDateString = Draftist_helperGetNewDueDateFromPrompt(selectedTasks.map((task) => task.content).join("\n"))
1851 | for (task of selectedTasks) {
1852 | if (!Draftist_updateDueDateOfTask({
1853 | todoist: todoistObj,
1854 | taskToUpdate: task,
1855 | newDueDateString: newDueDateString
1856 | })) {
1857 | // not needed, update function will already display the error
1858 | //let lastError = Draftist_getLastTodoistError(todoistObj);
1859 | //Draftist_failAction("update due date", "Todoist returned error:\n" + lastError)
1860 | return false
1861 | }
1862 | updatedTasksCount = updatedTasksCount + 1;
1863 | }
1864 | Draftist_succeedAction("update due date", false, "updated " + updatedTasksCount + " tasks")
1865 | return true;
1866 | }
1867 |
1868 | /**
1869 | * Draftist_resolveSelectedTasksFromFilter - resolves the selected tasks from a filter
1870 | *
1871 | * @param {String} filterString - a valid todoist filter string
1872 | * @returns true if all selected tasks were resolved successfully, false if something failed
1873 | */
1874 | function Draftist_resolveSelectedTasksFromFilter(filterString) {
1875 | let tasksFromFilter = Draftist_getTodoistTasksFromFilter(filterString)
1876 | // early retrun if no task was retrieved
1877 | if (!tasksFromFilter) {
1878 | return false;
1879 | }
1880 | // let the user select the tasks
1881 | let selectedTasks = Draftist_selectTasksFromTaskObjects(tasksFromFilter, true, "from filter \"" + filterString + "\"");
1882 | if (selectedTasks.length == 0) {
1883 | return false;
1884 | }
1885 | let todoist = new Todoist()
1886 | let resolvedTasksCount = 0;
1887 | for (task of selectedTasks) {
1888 | if (!todoist.closeTask(task.id)) {
1889 | const lastError = Draftist_getLastTodoistError(todoist);
1890 | Draftist_failAction("resolve tasks", "Todoist returned error: " + lastError)
1891 | return false;
1892 | }
1893 | resolvedTasksCount = resolvedTasksCount + 1;
1894 | }
1895 | Draftist_succeedAction("resolve tasks", false, "resolved " + resolvedTasksCount + " tasks");
1896 | return true;
1897 | }
1898 |
1899 | /**
1900 | * Draftist_deleteSelectedTasksFromFilter - deletes the selected tasks from a filter
1901 | *
1902 | * @param {String} filterString - a valid todoist filter string
1903 | * @returns true if all selected tasks were resolved successfully, false if something failed
1904 | */
1905 | function Draftist_deleteSelectedTasksFromFilter(filterString) {
1906 | let tasksFromFilter = Draftist_getTodoistTasksFromFilter(filterString)
1907 | // early retrun if no task was retrieved
1908 | if (!tasksFromFilter) {
1909 | return false;
1910 | }
1911 | // let the user select the tasks
1912 | let selectedTasks = Draftist_selectTasksFromTaskObjects(tasksFromFilter, true, "from filter \"" + filterString + "\"");
1913 | if (selectedTasks.length == 0) {
1914 | return false;
1915 | }
1916 | let todoist = new Todoist()
1917 | let deletedTasksCount = 0;
1918 | for (task of selectedTasks) {
1919 | const settings = {
1920 | "method": "DELETE",
1921 | "url": "https://api.todoist.com/rest/v1/tasks/" + task.id
1922 | }
1923 | if (!todoist.request(settings)) {
1924 | const lastError = Draftist_getLastTodoistError(todoist);
1925 | Draftist_failAction("resolve tasks", "Todoist returned error: " + lastError)
1926 | return false;
1927 | }
1928 | deletedTasksCount = deletedTasksCount + 1;
1929 | }
1930 | Draftist_succeedAction("delete tasks", false, "deleted " + deletedTasksCount + " tasks");
1931 | return true;
1932 | }
1933 |
1934 |
1935 | /**
1936 | * @returns Boolean to indicate success of the Action
1937 | */
1938 | function Draftist_createProjectFromDraftsTitleAndAddLinksToDraft(todoist = new Todoist(), ) {
1939 | // get title of draft (use safe title)
1940 | let projectTitle = draft.processTemplate("[[safe_title]]").trim()
1941 | if(projectTitle.length == 0){
1942 | // ask for new title of project
1943 | let tPrompt = new Prompt()
1944 | tPrompt.title = "Set Project Title"
1945 | tPrompt.addTextField("title","","",{wantsFocus: true})
1946 | tPrompt.addButton("set title")
1947 | if(tPrompt.show()){
1948 | projectTitle = tPrompt.fieldValues["title"]
1949 | if(projectTitle.trim().length == 0){
1950 | // still empty -> fail
1951 | let msg = "project title can't be empty"
1952 | Draftist_failAction("create linked project", msg)
1953 | return false;
1954 | }
1955 | // prepend project title
1956 | draft.prepend("# " + projectTitle);
1957 | }
1958 | }
1959 | let result = todoist.createProject({
1960 | "name": projectTitle
1961 | })
1962 | if (!result) {
1963 | const lastError = Draftist_getLastTodoistError(todoist);
1964 | Draftist_failAction("create project", "Todoist returned error: " + lastError)
1965 | return false;
1966 | }
1967 |
1968 | let projectId = result.id
1969 | // set projectId as template tag to be used later
1970 | draft.setTemplateTag("createdProjectId",projectId)
1971 |
1972 | let text = `
1973 | Todoist Project:
1974 | - [🌐](https://todoist.com/app/project/${projectId})
1975 | - [📱](todoist://project?id=${projectId})`
1976 |
1977 | draft.insert(text, 1)
1978 | draft.update()
1979 |
1980 | let linkedTaskContent = `* Project Draft: [${projectTitle}](${draft.permalink})`
1981 |
1982 | if (!todoist.createTask({
1983 | "content": linkedTaskContent,
1984 | "project_id": projectId
1985 | })) {
1986 | const lastError = Draftist_getLastTodoistError(todoist);
1987 | Draftist_failAction("create task in project", "Todoist returned error: " + lastError)
1988 | return false;
1989 | }
1990 |
1991 | Draftist_succeedAction("create linked project", false, "created linked project")
1992 | return true
1993 | }
1994 |
1995 |
1996 | // #############################################################################
1997 | // SETTINGS AND DATA STORAGE
1998 | // #############################################################################
1999 |
2000 | /**
2001 | * defaultSettingsParams: these are the default settings for the Action Group
2002 | */
2003 | const defaultSettingsParams = {
2004 | "dataStoreUpdateInterval": 24,
2005 | "taskLinkTypes": ["app", "web"],
2006 | "taskImportContents": ["appLink", "webLink", "projectName", "priority", "labels"]
2007 | }
2008 |
2009 |
2010 | /**
2011 | * static definition of the names for the storage Drafts
2012 | */
2013 | const settingsDraftName = "Draftist Action Group Settings";
2014 | const dataStoreDraftName = "Draftist Todoist Data Store";
2015 |
2016 | /**
2017 | * global variables that are used within the various functions to access current settings - stored in variables to quicker access them in different use cases
2018 | */
2019 | let activeSettings = undefined;
2020 | let lastUpdated = undefined;
2021 | let projects = undefined;
2022 | let labels = undefined;
2023 | let sections = undefined;
2024 | let projectsNameToIdMap = new Map();
2025 | let projectsIdToNameMap = new Map();
2026 | let labelsNameToIdMap = new Map();
2027 | let labelsIdToNameMap = new Map();
2028 | const settingsFilePath = "/Library/Scripts/DraftistSettings.json"
2029 | const dataStoreFilePath = "/Library/Scripts/DraftistDataStore.json"
2030 |
2031 |
2032 | /**
2033 | * Draftist_getSettingsFromFile - reads the settings from the stored file, creates file with default settings if not already present
2034 | * @returns {Object} the settings object stored in the settings file
2035 | */
2036 | function Draftist_getSettingsFromFile() {
2037 | // iCloud file manager
2038 | let fmCloud = FileManager.createCloud();
2039 | const readResult = fmCloud.readJSON(settingsFilePath);
2040 | if (!readResult) {
2041 | // file is not existing, write initial Data
2042 | fmCloud.writeJSON(settingsFilePath, defaultSettingsParams)
2043 | return defaultSettingsParams;
2044 | } else {
2045 | // read settings into global variable
2046 | return readResult;
2047 | }
2048 | }
2049 |
2050 | /**
2051 | * Draftist_writeActiveSettingsToFile - writes the current active loaded settings to the settings file
2052 | * @returns {Boolean} true if writing was successfull or no settings are loaded right now; false if writing failed
2053 | */
2054 | function Draftist_writeActiveSettingsToFile() {
2055 | if (!activeSettings) {
2056 | // active Settings are undefined, nothing to write (but nothing failed either)
2057 | return true;
2058 | }
2059 | // iCloud file manager
2060 | let fmCloud = FileManager.createCloud();
2061 | // write active Settings to file
2062 | const writeResult = fmCloud.writeJSON(settingsFilePath, activeSettings);
2063 | return writeResult;
2064 | }
2065 |
2066 | /**
2067 | * Draftist_loadCurrentConfigurationSettings - loads the current settings stored in the settings file into the live variable of Draftist
2068 | */
2069 | function Draftist_loadCurrentConfigurationSettings() {
2070 | activeSettings = Draftist_getSettingsFromFile();
2071 | }
2072 |
2073 |
2074 | /**
2075 | * Draftist_restoreDefaultSettings - funtion to restore the default settings
2076 | *
2077 | */
2078 | function Draftist_restoreDefaultSettings() {
2079 | // iCloud file manager
2080 | let fmCloud = FileManager.createCloud();
2081 | // write active Settings to file
2082 | const writeResult = fmCloud.writeJSON(settingsFilePath, defaultSettingsParams);
2083 | if (!writeResult) {
2084 | Draftist_failAction("restore default settings", "failed writing settings");
2085 | } else {
2086 | Draftist_infoMessage("", "restored default settings")
2087 | }
2088 | }
2089 |
2090 |
2091 | /**
2092 | * Draftist_Settings - open the settings for Draftist - the user can either restore the default settings or change the current active settings
2093 | */
2094 | function Draftist_Settings() {
2095 | // load current settings
2096 | Draftist_loadCurrentConfigurationSettings();
2097 |
2098 | // ask the user if the default settings shall be restored or changes to the settings shall be made.
2099 |
2100 | let pOptions = new Prompt();
2101 | pOptions.title = "Draftist Settings"
2102 | pOptions.addButton("restore default settings");
2103 | pOptions.addButton("change current settings");
2104 | if (pOptions.show()) {
2105 | // user selected an option
2106 | // execute the corresponding functions based on the selection
2107 | switch (pOptions.buttonPressed) {
2108 | case "restore default settings":
2109 | Draftist_restoreDefaultSettings();
2110 | break;
2111 | case "change current settings":
2112 | Draftist_changeConfigurationSettings();
2113 | break;
2114 | }
2115 | }
2116 | }
2117 |
2118 | /**
2119 | * Draftist_changeConfigurationSettings - function to change the current active settings of Draftist and store them in the settings Draft
2120 | *
2121 | */
2122 | function Draftist_changeConfigurationSettings() {
2123 | let proceedSettingsPrompts = true;
2124 | if (proceedSettingsPrompts) {
2125 | // setting for local storage usage
2126 | let pStore = new Prompt();
2127 | pStore.title = "update inteval for todoist data"
2128 | pStore.message = "the action group stores todoist data locally in a Draft, this includes e.g. project/label names, ids which are necessary to quickly add tasks to projects (or add labels to tasks), the local storage speeds up creating tasks a lot. The data will be updated in the time period of your choice (in hours default: every 24h)";
2129 | pStore.addSelect("updateInterval", "update interval [h]", ["1", "2", "5", "10", "24", "36", "48"], [activeSettings["dataStoreUpdateInterval"].toString()], false)
2130 | pStore.addButton("Apply");
2131 | if (pStore.show()) {
2132 | // user selected to apply the settings
2133 | // store the setting in current active settings variable
2134 | activeSettings["dataStoreUpdateInterval"] = parseInt(pStore.fieldValues["updateInterval"])
2135 | } else {
2136 | proceedSettingsPrompts = false;
2137 | }
2138 | }
2139 | if (proceedSettingsPrompts) {
2140 | // settings for crosslinked Task Urls
2141 | let pTaskLinks = new Prompt();
2142 | pTaskLinks.title = "task link settings"
2143 | pTaskLinks.message = "the crosslink task actions can append / prepend links to the created tasks in Todoist to the current draft. App links only work reliably on iOS / iPadOS - If you want to use task links on macOS, too you need to enable web links"
2144 | pTaskLinks.addSelect("linkTypes", "link types", ["app", "web"], activeSettings["taskLinkTypes"], true)
2145 | pTaskLinks.addButton("Apply");
2146 | if (pTaskLinks.show()) {
2147 | activeSettings["taskLinkTypes"] = pTaskLinks.fieldValues["linkTypes"]
2148 | } else {
2149 | proceedSettingsPrompts = false;
2150 | }
2151 | }
2152 |
2153 | if (proceedSettingsPrompts) {
2154 | // settings for import task contents
2155 | let pImportContents = new Prompt();
2156 | pImportContents.title = "task import content settings"
2157 | pImportContents.message = "each imported tasks will contain the information you select in this prompt"
2158 | pImportContents.addSelect("taskImportContents", "task import contents", ["appLink", "webLink", "projectName", "priority", "labels"], activeSettings["taskImportContents"], true)
2159 | pImportContents.addButton("Apply");
2160 | if (pImportContents.show()) {
2161 | activeSettings["taskImportContents"] = pImportContents.fieldValues["taskImportContents"]
2162 | } else {
2163 | proceedSettingsPrompts = false;
2164 | }
2165 | }
2166 |
2167 | if (!Draftist_writeActiveSettingsToFile()) {
2168 | Draftist_failAction("change settings", "unexpected failure, please try again and if the issue persists, contact @FlohGro with a description to reproduce the issue.")
2169 | } else {
2170 | Draftist_infoMessage("", "settings updated")
2171 | }
2172 | }
2173 |
2174 | /**
2175 | * Draftist_getDataStoreFromFile - reads the stored data from the directory. updates the data if the file is not existing
2176 | * @returns true when the file was read successfully
2177 | */
2178 | function Draftist_getDataStoreFromFile() {
2179 | // iCloud file manager
2180 | let fmCloud = FileManager.createCloud();
2181 | const readResult = fmCloud.readJSON(dataStoreFilePath);
2182 | if (!readResult) {
2183 | // file is not existing, update the data which will write them to the file and then run this function again
2184 | Draftist_updateStoredTodoistData();
2185 | return Draftist_getDataStoreFromFile();
2186 | // if (!Draftist_updateStoredTodoistData()) {
2187 | // Draftist_failAction("get data store", "unexpected failure, please try again and if the issue persists, contact @FlohGro with a description to reproduce the issue.")
2188 | // }
2189 | } else {
2190 | // return the read object
2191 | return readResult
2192 | }
2193 | }
2194 |
2195 | function Draftist_writeDataStoreToFile(dataToStore) {
2196 | if (!dataToStore) {
2197 | // lastUpdated, nothing to write (but nothing failed either)
2198 | return true;
2199 | }
2200 | // iCloud file manager
2201 | let fmCloud = FileManager.createCloud();
2202 | // write data to file
2203 | const writeResult = fmCloud.writeJSON(dataStoreFilePath, dataToStore);
2204 | return writeResult;
2205 | }
2206 |
2207 |
2208 | /**
2209 | * Draftist_updateTodoistDataIfUpdateIntervalExceeded - checks if an update of the locally stored Todoist data is needed based on the settings of the dataStoreUpdateInterval
2210 | */
2211 | function Draftist_updateTodoistDataIfUpdateIntervalExceeded() {
2212 | // check if variable is defined (was initialized) otherwise load settings from draft
2213 | if (!lastUpdated) {
2214 | Draftist_getStoredTodoistData()
2215 | }
2216 | const now = Date.now();
2217 | let tDiffLastUpdate = now - lastUpdated
2218 | // check if variable is defined (was initialized) otherwise load settings from draft
2219 | if (!activeSettings) {
2220 | Draftist_loadCurrentConfigurationSettings()
2221 | }
2222 | let updateInterval = activeSettings["dataStoreUpdateInterval"] * 3600000;
2223 |
2224 | if (tDiffLastUpdate > updateInterval) {
2225 | // update is necessary
2226 | Draftist_updateStoredTodoistData();
2227 | }
2228 | }
2229 |
2230 |
2231 | /**
2232 | * Draftist_updateStoredTodoistData - updates the locally stored todoist data in the data store file
2233 | *
2234 | * @param {Todoist} todoist? - the todoist object to use
2235 | */
2236 | function Draftist_updateStoredTodoistData(todoist = new Todoist()) {
2237 | // retrieve data from todoist
2238 | const projects = todoist.getProjects();
2239 | const sections = todoist.getSections();
2240 | const labels = todoist.getLabels();
2241 | const updatedTimeUnixMilliseconds = Date.now();
2242 |
2243 | // create object with all todoist data
2244 | const todoistDataToStore = {
2245 | "lastUpdated": updatedTimeUnixMilliseconds,
2246 | "projects": projects,
2247 | "sections": sections,
2248 | "labels": labels
2249 | }
2250 | if (!Draftist_writeDataStoreToFile(todoistDataToStore)) {
2251 | Draftist_failAction("get data store", "unexpected failure, please try again and if the issue persists, contact @FlohGro with a description to reproduce the issue.")
2252 | }
2253 | Draftist_infoMessage("", "updated local Todoist data");
2254 | }
2255 |
2256 |
2257 | /**
2258 | * Draftist_getStoredTodoistData - function to retrieve the stored Todoist Data from the data store file - the stored data will be updated if the dataStoreUpdateInterval was exceeded and the stored data will be loaded into the global variables to be accessible for all other functions
2259 | */
2260 | function Draftist_getStoredTodoistData() {
2261 |
2262 | const storedData = Draftist_getDataStoreFromFile();
2263 |
2264 | lastUpdated = parseInt(storedData["lastUpdated"]);
2265 | //projects = storedData["projects"]
2266 | projects = storedData["projects"].map((project) => {
2267 | return [project["name"], project["id"]];
2268 | })
2269 | labels = storedData["labels"].map((label) => {
2270 | return [label["name"], label["id"]];
2271 | })
2272 | sections = storedData["sections"].map((section) => {
2273 | return [section["name"], section["id"]];
2274 | })
2275 | for (p of projects) {
2276 | projectsNameToIdMap.set(p[0], p[1])
2277 | projectsIdToNameMap.set(p[1], p[0])
2278 | }
2279 | for (l of labels) {
2280 | labelsNameToIdMap.set(l[0], l[1])
2281 | labelsIdToNameMap.set(l[1], l[0])
2282 | }
2283 |
2284 | // update data from Todoist if necessary
2285 | Draftist_updateTodoistDataIfUpdateIntervalExceeded();
2286 | }
2287 |
2288 |
2289 | /**
2290 | * Draftist_helperDraftistActionReplicator - opens the installURL for the selected Action of the Draftist Action Group to easily duplicate an Action of Draftist into another ActionGroup. The user has to manually rename the created Action afterwards.
2291 | *
2292 | * @return {undefined} always returns undefined
2293 | */
2294 | function Draftist_helperDraftistActionReplicator() {
2295 | const replicatorOmitList = ["Draftist Instructions", "Draftist Setup/Update", "Draftist", "Draftist Settings", "Draftist Action Replicator", "update local Todoist data"];
2296 | const actionGroup = ActionGroup.find("Draftist");
2297 | if (!actionGroup) {
2298 | // ActionGroup not found
2299 | Draftist_failAction("replicate Action", "Draftist Action Group name was changed")
2300 | return undefined
2301 | }
2302 | let pAction = new Prompt();
2303 | pAction.title = "select action to replicate";
2304 | for (action of actionGroup.actions) {
2305 | if (action.isSeparator) {
2306 | // nothing to be done
2307 | } else {
2308 | if (replicatorOmitList.indexOf(action.name) == -1) {
2309 | pAction.addButton(action.name, action)
2310 | }
2311 | }
2312 | }
2313 | if (!pAction.show()) {
2314 | Draftist_cancelAction("replicate Draftist Action", "user cancelled")
2315 | return undefined
2316 | }
2317 | const actionToReplicate = pAction.buttonPressed;
2318 | app.openURL(actionToReplicate.installURL)
2319 | return undefined
2320 | }
2321 |
2322 | /**
2323 | * Draftist_updateDraftist - presents a prompt to let the user select to update either the Action Group (by opening the link) or the Draftist.js file. To let the user check the latest version, also a "view" option is included which opens the file in the repository
2324 | */
2325 | function Draftist_updateDraftist() {
2326 | let pConfirmationPrompt = new Prompt()
2327 | pConfirmationPrompt.title = "Update Draftist";
2328 | pConfirmationPrompt.message = "This Action can update the \"Draftist.js\" file to the newest version of the GitHub repository.\nThis is necessary for bug fixes and version updates\n\n\nTo update the Draftist Action Group itself you need to update (reinstall) Draftist from Drafts Action Directory.\nThis will update Draftist to new releases with new Actions in the Action Group\n\n\nTo view the latest version you can open it in the repository and check out the latest changes there\n\nSelect what you want to do:"
2329 | pConfirmationPrompt.addButton("View newest version", "view")
2330 | pConfirmationPrompt.addButton("Update Draftist.js", "updateJs")
2331 | pConfirmationPrompt.addButton("Update Draftist Action Group", "updateAG")
2332 | if (pConfirmationPrompt.show()) {
2333 | switch (pConfirmationPrompt.buttonPressed) {
2334 | case "view":
2335 | app.openURL("https://github.com/FlohGro-dev/Draftist/blob/main/Draftist.js", true);
2336 | break;
2337 | case "updateJs":
2338 | Draftist_setupOrUpdateDraftistJsFilte();
2339 | break;
2340 | case "updateAG":
2341 | app.openURL("https://directory.getdrafts.com/g/1wK", false);
2342 | break;
2343 | }
2344 | }
2345 | }
2346 |
2347 | /**
2348 | * Draftist_setupOrUpdateDraftistJsFilte - this Action updates the Draftist.js file in the iCloud directory of the Drafts/Library folder to the latest version from GitHub
2349 | * @returns true if update successful, false if update was not performed successfully
2350 | */
2351 | function Draftist_setupOrUpdateDraftistJsFilte() {
2352 | const filename = "Draftist.js"
2353 | const subfoldername = "Scripts"
2354 | const filepath = "/Library/" + subfoldername + "/"
2355 |
2356 | // file url "https://github.com/FlohGro-dev/Draftist/blob/main/Draftist.js"
2357 | // need the raw url to get the files content
2358 | const draftistSourceUrl = "https://raw.githubusercontent.com/FlohGro-dev/Draftist/main/Draftist.js"
2359 | let fmCloud = FileManager.createCloud();
2360 | fmCloud.createDirectory(subfoldername, "/Library/");
2361 |
2362 | http = new HTTP();
2363 | // get the file
2364 | let requestResult = http.request({
2365 | "url": draftistSourceUrl,
2366 | "method": "GET"
2367 | });
2368 | // check if the result was successful
2369 | if (requestResult.success) {
2370 | fmCloud.writeString(filepath + filename, requestResult.responseText)
2371 | } else {
2372 | Draftist_failAction("setup or update", "download failed")
2373 | return false;
2374 | }
2375 | Draftist_succeedAction("setup/update Draftist", true, "downloaded latest version")
2376 | return true;
2377 | }
2378 |
2379 |
2380 | // dev part
2381 |
2382 | // create a function that can retrieve all projects with their name and internal link in a markdown format
2383 | function Draftist_TEST_createProjectsMdList() {
2384 | Draftist_updateStoredTodoistData()
2385 | if (projectsNameToIdMap.size == 0) {
2386 | Draftist_getStoredTodoistData();
2387 | }
2388 | let sortedProjectNameMap = new Map([...projectsNameToIdMap].sort((a, b) => String(a[0]).localeCompare(b[0])))
2389 |
2390 | let projectMdLinks = []
2391 |
2392 | for(const [pName, pId] of projectsNameToIdMap){
2393 | let str = "[" + pName + "](todoist://project?id=" + pId + ")"
2394 | // find related draft
2395 | let foundDrafts = Draft.queryByTitle(pName)
2396 | let dToUse = undefined
2397 | if(foundDrafts.length == 0){
2398 | // no draft found, thats ok
2399 | } else if(foundDrafts.length == 1){
2400 | // found just one draft -> perfect!
2401 | dToUse = foundDrafts[0]
2402 | } else {
2403 | // found several drafts, not that good :D
2404 | let p = new Prompt()
2405 | p.title = pName
2406 | p.message = "found several drafts, select one:"
2407 | for(d of foundDrafts){
2408 | p.addButton(d.displayTitle,d)
2409 | }
2410 | if(p.show){
2411 | dToUse = p.buttonPressed
2412 | }
2413 | }
2414 | if(dToUse){
2415 | str = str + " [" + dToUse.displayTitle + "](" + dToUse.permalink + ")"
2416 | }
2417 | projectMdLinks.push(str)
2418 | }
2419 |
2420 | let d = new Draft()
2421 | d.content = "# Todoist Project MD Links List\n\n" + projectMdLinks.join("\n")
2422 | d.update()
2423 | editor.load(d)
2424 |
2425 | }
2426 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 FlohGro
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Draftist
2 |
3 | Draftist - a [Drafts](https://getdrafts.com) Action Group to integrate with [Todoist](https://todoist.com).
4 |
5 | **created by [@FlohGro](https://twitter.com/FlohGro)**
6 |
7 | - **Website:** [flohgro.com](https://flohgro.com)
8 | - **Drafts Forums:** [@FlohGro](https://forums.getdrafts.com/u/flohgro/summary)
9 | - **Twitter:** [@FlohGro](https://twitter.com/FlohGro)
10 |
11 | This repository contains the underlying functions and the documentation for the Draftist Action Group.
12 | You can install the Draftist Action Group from the Drafts directory: [Draftist](https://directory.getdrafts.com/g/1wK)
13 |
14 | If you encounter any issues please open an issue in the repository or reach out to me in the [Drafts Forums Post about Draftist](https://forums.getdrafts.com/t/draftist-a-drafts-action-group-for-todoist/12674) or on [Twitter](https://twitter.com/FlohGro) ✌🏽
15 |
16 | ## Draftist Instructions
17 |
18 | To read through the instructions of Draftist look into the dedicated file in this repository: [Draftist Instructions](https://github.com/FlohGro-dev/Draftist/blob/main/Draftist%20Instructions.md)
19 |
20 | ## Support Development
21 |
22 | I developed these functions and the Action Group in my free time to help myself and you improve workflows and remove friction from processes. 🚀
23 |
24 | Draftist is completely free to use for you. However if this Action Group is useful for you and supports your workflows you can give something back to support development.
25 | I enjoy a good coffee ☕️ (weather at home or in an actual coffee shop) and love pizza 🍕.
26 | You can choose the amount you want to donate on those platforms.
27 |
28 |
29 |
30 |
31 |
32 | ## Changelog
33 |
34 | ### Draftist 1.0 - initial Release
35 |
36 | *this is the initial version of Draftist*
37 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |