Action

crosslink Todoist project & Craft document

Posted by FlohGro, Last update 9 months ago

UPDATES

10 months ago

changed default behavior to don’t use a template; added webview link

created by @FlohGro / more on my Blog

crosslink Todoist project & Craft document

This action will create a cross-linked project between Todoist and Craft.

The new project in Todoist will contain a link to the document in Craft and vice-versa.

The document in Craft will be created from a configurable template Draft.

Running the action will open a prompt asking you for the name of the project - this prompt will be prefilled with the current drafts content but you can set any name you like (the current draft will remain unchanged).

known “issue”: if you quickly open the link to the Todoist project after the action prepended the link to the project to the Craft document, your Todoist app may show an error telling you that the project could not be found. The reason for this is, that Todoist needs to sync the created project to your app first. After the project was synced, the error won’t occur again.

[Configuration]

You need to follow these steps until you can use the action.

Define the project template draft

The action can be configured to use a template for all your project documents (if you want that all of them have the same structure).

If you don’t want to use a project template draft (the created Craft document will be empty besides the link to the Todoist project). There is no need to change something after installing the action since the template usage is disabled by default.

If you want to use a template for the project document in Craft, create a new draft and give it a meaningful title (first line) and maybe add some tags to the draft to keep your library structured.

Then setup the template in Markdown format (note that the title (first line) of the template draft will not be pushed to the Craft document)

When you finished the template tap the „info“ button in the editor and then tap the button titled „UUID“. This will copy the UUID of that draft.

Next, edit the script step of this action and change the variable in line 3 to true: const templateDraftAvailable = true;. Then find line 5 const templateDraftUUID = "unconfigured“; and select the word unconfigured and paste the UUID you just copied (attention: don’t delete the quotation marks).

If you don’t want to use the (any of my) action for different Craft spaces there is no further configuration needed. When you first run any of my Craft actions it will ask you to store the space id of your Craft space. This is a one time action and you don’t need to do it for any other oof my Craft actions you install.

To retrieve your spaceId just copy the deeplink of any document in that space (refer to the Craft Support Page when you don’t know how to do that). Paste the copied id into a draft and you will se a link similar to this: “craftdocs://open?blockId=[the block id]&spaceId=[the spaceId]” - find the character combination “[the spaceId]” after the “spaceId=” and copy it and paste it into the prompt of this action.

To use these actions with different spaces you need to duplicate the action for each space you want to use it. I recommend to e.g. add a suffix to the action name to describe the space for which you configure it.

The action uses Drafts possibility to store credentials to distinguish different spaces. When you duplicate the action for another space you have to change the name of the credential. Therefore you need edit this line const spaceIdCredentialName = "CraftDocumentSpace" in the script step of the action and change the CraftDocumentSpace to something different (e.g. describe the space in a suffix like „CraftDocumentSpacePersonal" or „CraftDocumentSpaceWork“. If you use several of my Craft actions you should use the same credential name in all of them.

[Usage]

After you configured the action properly - just run it when you want to setup a new project from whatever draft you currently opened.

The prompt will appear and you can use either the content of the current draft as the project name or just type a new name.

The action will then create the project in Todoist, retrieve its id and create a new craft document (eventually with your configured template) and link that project in the document. Then the action will create an uncompletable task in the new Todoist project containing a clickable link to the project document in Craft.

note: the Craft document will actually contain two links to Todoist. The name of the project will be a link which can be used on mobile (iOS / iPadOS) to directly open the project in the Todoist app. the „Webview“ link is necessary for macOS since the mobile url is not working properly on the Mac.


If you find this useful and want to support me you can

Buy Me A Coffee

Steps

  • script

    // is there an additional Draft Template which shall be added as content to the note?
    // set this to true if you want to use a template draft / if you don't want to use a template, then set it to false (no "" around the true / false)
    const templateDraftAvailable = false;
    // copy the uuid of your template draft for project documents and paste it between the quotation marks
    const templateDraftUUID = "unconfigured";
    
    // ------------------------------------------------------------------------
    // END OF CONFIGURATION
    // ANY CHANGES BELOW CAN LET THE ACTION FAIL - ONLY CHANGE THINGS IF YOU KNOW WHAT YOU ARE DOING
    // ------------------------------------------------------------------------
    
    
    
    if (templateDraftAvailable == true && templateDraftUUID == "unconfigured") {
        let errStr = "template Draft should be used but uuid is unconfigured, read the description of the action and configure it correctly."
        context.fail(errStr);
        app.displayErrorMessage("invalid configuration");
    } else {
    
        var spaceId = getCraftSpaceIdToUse();
        if (spaceId) {
    
            // create Todoist object
            var todoist = Todoist.create();
    
            // define project name
            let newProjectName = setProjectName()
    
            if (newProjectName) {
    
                let createdProject = createTodoistProject(newProjectName);
    
                if (createdProject) {
                    let projectLinkStringToDocument = createCraftDocumentAndLinkedprojectString(newProjectName, createdProject["id"])
                    if (projectLinkStringToDocument) {
                        createTaskInTodoistProject(projectLinkStringToDocument, createdProject["id"])
                    }
                }
            } else {
                // do error handling
                context.cancel("no project name set");
                app.displayWarningMessage("aborted; didn't select a project name");
            }
    
    
        } else {
            // do error handling
            context.cancel("Craft spaceId is not configured");
            app.displayWarningMessage("Craft spaceId is not configured");
        }
    }
    // ------------------------------------------------------------------------
    // FUNCTIONS
    // ------------------------------------------------------------------------
    
    
    function setProjectName() {
        let projectName = "";
        if (draft.content.length != 0) {
            projectName = draft.content;
        }
        var p = Prompt.create();
        p.title = "Project Name";
    
        p.addTextField("userProjectName", "", projectName, {
            wantsFocus: true
        });
    
        p.addButton("Set Project Name");
    
        if (p.show()) {
    
            projectName = p.fieldValues["userProjectName"]
            return projectName
        } else {
            return false
        }
    }
    
    
    function createTodoistProject(projectName) {
    
        // create the project in todoist
        let createProjectResponse = todoist.createProject({
            "name": projectName
        })
    
        if (!createProjectResponse) {
            let message = "Failed to add project to todoist: " + todoist.lastError;
            console.log(message)
            context.fail(message);
            app.displayErrorMessage(message);
        } else {
            let message = "successfully added project in todoist with name: " + projectName;
            console.log(message)
            // return relevant parameters of the
            return {
                "id": createProjectResponse["id"],
                "url": createProjectResponse["url"]
            }
        }
    }
    
    function createCraftDocumentAndLinkedprojectString(projectName, todoistProjectID) {
    
        const craftCreateBaseURL = "craftdocs://x-callback-url/createdocument?";
    
        const todoistShowProjectUrl = "todoist://project?id=" + todoistProjectID;
    
    	const todoistShowProjectUrlWebView = "https://todoist.com/showProject?id=" + todoistProjectID;
    	
        const projectLinkText = "> Project in Todoist: [" + projectName + "](" + todoistShowProjectUrl + ") ([Webview](" + todoistShowProjectUrlWebView + "))";
        let unencodedTitle = projectName;
        let title = encodeURIComponent(projectName);
    
        let templateContent = "";
        if (templateDraftAvailable) {
            let templateDraft = Draft.find(templateDraftUUID);
            templateContent = templateDraft.processTemplate("[[body]]")
            templateContent = "\n" + templateContent
        }
    
        let content = encodeURIComponent(projectLinkText + templateContent);
    
        var cbCreateCraftNote = CallbackURL.create()
        cbCreateCraftNote.baseURL = craftCreateBaseURL
        cbCreateCraftNote.addParameter("spaceId", spaceId)
        cbCreateCraftNote.addParameter("title", title)
        cbCreateCraftNote.addParameter("content", content)
        cbCreateCraftNote.addParameter("folderId", "")
        cbCreateCraftNote.waitForResponse = true
    
        let craftCreateReturn = cbCreateCraftNote.open()
    
        if (craftCreateReturn == true) {
            console.log("Craft note successfully created")
            let craftCreateResult = cbCreateCraftNote.callbackResponse
            var craftNoteLink = craftCreateResult.link
    
            let projectNoteStr = "Craft Note: " + craftNoteLink
            let projectTitle = "* **Craft Document: [" + unencodedTitle + "](" + craftNoteLink + ")**"
            return projectTitle;
    
        } else {
            console.log("Craft note result:" + cb.status + " " + cb.callbackResponse)
            if (cb.status == "cancelled") {
                context.cancel()
            } else {
                context.fail()
            }
            return
        }
    
    }
    
    function createTaskInTodoistProject(projectContent, projectId) {
        // create the task in todoist
        let createprojectResponse = todoist.createTask({
            "content": projectContent,
            "project_id": projectId
        })
    
        if (!createprojectResponse) {
            let message = "Failed to add project to todoist: " + todoist.lastError;
            console.log(message)
            context.fail(message);
            app.displayErrorMessage(message);
            return false;
        } else {
            let message = "successfully added project in todoist with content: " + projectContent + "to project with id: " + projectId;
            console.log(message)
            // return relevant parameters of the
            return true;
        }
    }
    
    function getCraftSpaceIdToUse() {
        let credential = Credential.create("CraftDocumentSpace", "Credential to store the spaceId of the space you want to use in Drafts.\ninsert your spaceId into the TextField below. \n\nNOTES: \n- this is a one time action, you don't need to do it everytime\n- Multiple spaces are currently not supported with this spaceId credential helper");
        credential.addTextField("spaceId", "spaceId");
        if (credential.authorize()) {
            return credential.getValue("spaceId");
        } else {
            let errorStr = "failed storing / retrieving space Id with credential"
            app.displayErrorMessage(errorStr);
            context.fail(errorStr);
            console.log(errorStr);
            return false;
        }
    }

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.