Action

todoist project from list

Posted by FlohGro, Last update over 3 years ago

Todoist project from list

This action creates a new project in your todoist account from a template draft.
The template Draft has to be specified by the UUID, copy this from your template list and configure it in the script step of the action.
The action parses this draft and creates a new project where you can configure the name. A prompt will ask you for the name and the action will append a configurable „list-name“.
My current use case is a packing list. The template with all possible things I need to carry to a trip is stored in a Draft.
I divided the packing list with sub-headings for e.g. „Clothing“ with elements for this category.
The action parses each line, sub-headings will become to bold written parent tasks and all tasks below a sub-heading will become child tasks of this sub-heading.
The required format for a draft which can be parsed by this action looks like this:

[any title line]                - this line will be ignored, its just your title for the list
[maybe empty line]              - any empty line in the source draft will be stripped by a regex replacement
## [subheading 1]               - this subheader is the first parent task
- [ ] [item 1 of subheading 1]  - this will be the first subtask of the previous subheading
- [ ] [item x of subheading 1]  - this will be the xth subtask of the previous subheading
[maybe empty line]              - any empty line in the source draft will be stripped by a regex replacement
## [subheading y]               - this subheader is the yth parent task
- [ ] [item 1 of subheading y]  - this will be the first subtask of the subheading y

You can of course use several subheadings to create as many parent tasks with subtasks as you want.
If you dont want to use parent tasks at all, don’t create sub-headings in your draft.

Depending on the amount of items in your list, this action will take some time to run.
This is mainly justified by the limits of todoists REST API where the script is only allowed to perform 50 requests per minute. Therefore I implemented a sleep function which adapts dynamically to the amount of tasks in your list. I integrated a roughly estimated calculation for the process time and the user will see an info message from the app every 5 tasks.

If you have any issues please reach out to me in the forum @FLohGro

Steps

  • script

    // list to todoist
    
    // ----------------------------------------------------------------------------------------
    // start of user definitions
    
    // the uuid of the draft which contains the list. The list is considered to have a special "syntax" syntax is shown after the user definitions
    const uuidOfSourceDraft = "[uuid of source draft]"
    
    // this is the general name of the project in todoist, if you type a name into the prompt, this configured name will be appended after oyur given name (e.g. if you make a trip to "Big Sur" and type this into the prompt, the todoist project will be called "Big Sur - Packing List"
    const listNameForProject = "Packing List"
    
    // todoist REST API limit (https://developer.todoist.com/rest/v1/?python#limits)
    // needed to calculate a sleep time between the requests to the API, to be able to create every task on the list
    const todoistApiLimitPerMinute = 50
    // end of user definitions
    // ----------------------------------------------------------------------------------------
    
    // required "syntax" / format of the source draft
    /* ----------------------------------------------------------------------------------------
    [any title line]				- this line will be ignored, its just your title for the list
    [maybe empty line]				- any empty line in the source draft will be stripped by a regex replacement
    ## [subheading 1]				- this subheader is the first parent task
    - [ ] [item 1 of subheading 1]	- this will be the first subtask of the previous subheading
    - [ ] [item x of subheading 1]	- this will be the xth subtask of the previous subheading
    [maybe empty line]				- any empty line in the source draft will be stripped by a regex replacement
    ## [subheading y]				- this subheader is the yth parent task
    - [ ] [item 1 of subheading y]	- this will be the first subtask of the subheading y
    ---------------------------------------------------------------------------------------- */
    // you can of course use several subheadings to create as many parent tasks with subtasks as you want.
    // if you dont want to use a parent task at all, dont create subheadings in your draft
    // all subheadings will be created as tasks with the subheading as content in bold
    
    
    
    const listDraft = Draft.find(uuidOfSourceDraft);
    const listContent = listDraft.content
    
    var tasksText = listContent
    
    // remove all empty lines in text
    tasksText = tasksText.replace(/^\s*[\r\n]/gm, "")
    
    // remove title of draft
    tasksText = tasksText.split("\n").slice(1).join("\n")
    
    // replace all "## " and write the category in bold - marks a parent task
    tasksText = tasksText.replace(/##\s([^\n]+)/gm, `**$1**`)
    
    // replace all "- [ ] " with "" -> get rid of the taskboxes
    tasksText = tasksText.replace(/-\s\[[\sx]\]\s/gm, "")
    var tasks = tasksText.split("\n")
    
    let taskAmount = tasks.length - 1 // -1 because last line after split is empty (most times, does not matter in the end since this is just a rough calculation
    var millisecondsPerTask = 0
    var millisecondsPerTaskNoProcessTime = 500
    if (taskAmount <= todoistApiLimitPerMinute) {
    // just an estimation and hopefully todoist is faster than this - just a rough calculation
    } else {
        let minutesNeeded = taskAmount / todoistApiLimitPerMinute
        let millisecondsNeeded = minutesNeeded * 60 * 1000
        // just an estimation and hopefully todoist is faster than this - just a rough calculation
        let processTimePerTaskInMilliseconds = 500
        let millisecondsPerTaskNoProcessTime = millisecondsNeeded / taskAmount
        let millisecondsPerTask = (millisecondsNeeded / taskAmount) - processTimePerTaskInMilliseconds
    }
    
    // ask for name of new project with a prompt
    // Prompt
    
    var p = Prompt.create();
    p.title = "Project name";
    p.message = "name your trip";
    
    p.addTextField("projectName", "name:", "");
    p.addButton("Go");
    
    var con = p.show();
    
    var projectName = ""
    if (con) {
        projectName = p.fieldValues["projectName"];
    }
    
    if (projectName == "") {
        projectName += listNameForProject
    } else {
        projectName += " - " + listNameForProject
    }
    
    // create Todoist object and new project
    let todoist = Todoist.create()
    
    // create project
    var projectToCreate = {
        "name": projectName,
        "color": 41
    };
    
    var projectResult = todoist.createProject(projectToCreate)
    let projectID = projectResult["id"];
    
    
    var parentTaskID = -1
    var createdTasks = 0
    for (nTask of tasks) {
        if (nTask.length != 0) {
    
            if (nTask.startsWith("**")) {
                // its a new parent task
                // create task and store new parent task id
                parentTaskID = createTask(nTask, -1)
            } else {
                // its a (sub) task
                if (parentTaskID == -1) {
                    // parentTaskID is not set, create a "normal" task
                    createTask(nTask, -1)
                } else {
                    // parentTaskID is set, use it as parent for the new task
                    createTask(nTask, parentTaskID)
                }
            }
    
            createdTasks++;
            if (createdTasks % 5 == 0) {
                let remainingTime = ((taskAmount - createdTasks) * millisecondsPerTaskNoProcessTime) / 1000
                let infoMessage = createdTasks + " tasks created, " + remainingTime + "s remaining"
                app.displayInfoMessage(infoMessage)
            }
            sleep(millisecondsPerTask)
        }
    
    }
    
    
    function createTask(content, parentTaskID) {
        if (parentTaskID == -1) {
            // no parentTaskID given
            var taskToCreate = {
                "content": content,
                "project_id": parseInt(projectID)
            };
    
        } else {
            // task witn parent
            var taskToCreate = {
                "content": content,
                "project_id": parseInt(projectID),
                "parent_id": parentTaskID
            };
        }
        let task = todoist.createTask(taskToCreate);
        if (!todoist.lastError) {
            return task["id"]
        } else {
            logMessage = "failed adding task: " + content + " with error message " + todoist.lastError + " :(";
            context.fail(logMessage);
        }
    }
    
    function sleep(milliseconds) {
        const date = Date.now();
        let currentDate = null;
        do {
            currentDate = Date.now();
        } while (currentDate - date < milliseconds);
    }

Options

  • After Success Default
    Notification Info
    Log Level Info
Items available in the Drafts Directory are uploaded by community members. Use appropriate caution reviewing downloaded items before use.