Scriptable iOS Widget for Tasks Plugin

I’ve been trying to use Obsidian as a task manager with the tasks plugin for the past few weeks, but I really on having a widget for my tasks on the home screen. I used to use the iOS shortcuts app + Toolbox Pro for file access + a Pushcut widget, but it took took minutes to update the Widget and updates had to be manually triggered.
Now I finally managed to use my very basic JS skills get a Scriptable script working, which basically just searches all folders for md files, and scans them for a regular expression of a Tasks task to then display them on a widget. It works pretty well and updates in under 500ms. Anyone else got ideas for widgets?

This is really cool!

This kind of interaction—and quick task capture—are the only thing keeping some of my tasks in Things versus Obsidian. I like them in Obsidian because they’re contextual to where they came up. Things is quick to capture when I’m not in Obsidian.

Yeah, same for me actually, I also used things before, but having them in documents just adds a huge ammount of value to it, I even link affiliated PDFs sometimes.

Would you mind sharing your script? This looks promising.

Sure, I can share, but I had to retract some private stuff. Consider that I’m not good at programming at all though, this is made with very basic JS knowledge.

I attached the original script, but if you have more than 100 Notes, it will not work because of the memory limitations Apple puts on widgets. I fixed this by periodically running a whole script and writing the task list to a text file being read by the widget.

https://www.toptal.com/developers/hastebin/ekipirukuh.js

1 Like

Thanks.

Is there any way you can share your script again. It seems like the link is dead. I am trying to do something very similar

Yeah I would love if you could share the script again, this looks exactly like what I want to do, I cant seem to render the tasks list into a widget, I can only render as the raw markdown version - i.e. "

not done
due on today

"

Sorry I thought I would just replace my comment above as I have compressed the code a lot and it now runs very well

This is my code to do similar to what Lime did, however this is for running as an Extra Large Widget, and will sort my tasks into - Overdue, today, tomorrow etc. I am sure with some tweaks it can be changed into a smaller widget form.

As I have no experience with Javascript, I have used ChatGPT for help and with a lot of editing, this the resulting code that produces the above widget. Basically it first combs through my folders of daily notes; structure (Daily Notes/YYYY/MM-MMMM/note.md). Then for each note it reads every line to look for something like “- [ ] taskname” with or without a due date, and then sorts them into lists depending on when it is due. It then creates the widget by adding 5 stacks that each have a list of what is due when.

I have no idea if it will struggle as Lime said about the iCloud limits at above 100 notes - i do not have 100 notes yet.

Any feedback is welcome :slight_smile:

String.prototype.trimRight = function(charlist) {
  if (charlist === undefined) charlist = "\s";
  return this.replace(new RegExp("[" + charlist + "]+$"), "");
};

function createWidget(items, today) {
  
  let clippedItems = items.slice(0, 5);
  let linkRegex = /\[\[(.+)\]\]/;
	
  //set up basic widget things
  let w = new ListWidget();
  let titleStack = w.addStack();
  let title = titleStack.addText("Tasks");
  title.font = Font.boldSystemFont(18);
  title.textColor = Color.dynamic(Color.black(), Color.white());
  title.minimumScaleFactor = 1;
  w.addSpacer(2);

  let mainStack = w.addStack();
  mainStack.layoutHorizontally();
	
  //Adds stacks for each of my task groups
  for (let i = 0; i < 5; i++) {
    let stack = mainStack.addStack();
    stack.layoutVertically();
		//Add title
    let title = stack.addText(["Overdue", "Today", "Tomorrow", "Two Weeks", "Long Term"][i]);
    title.textColor = Color.purple();
    title.font = Font.boldSystemFont(15);
    title.minimumScaleFactor = 1;
    stack.addSpacer(3);
		
    //If no tasks then print a message
    if (clippedItems[i].length === 0) {
      let line = stack.addText(`Nothing ${["Overdue", "Due Today", "Due Tomorrow", "Due in Two Weeks", "Due Long Term"][i]}!`);
      line.font = Font.systemFont(12);
      line.textColor = Color.dynamic(Color.black(), Color.white());
    } else {
      //Else add the items from the clippeditems, only including up to 17 and adding ... if more
      let maxLen = 16;
      clippedItems[i] = clippedItems[i].slice(0, maxLen).map((item)  => {
        let color = linkRegex.test(item) ? new Color("#7e1dfb") : Color.dynamic(Color.black(), Color.white());
        item = item.replace(linkRegex, "$1");
        let line = stack.addText("> " + item);
        line.textColor = color;
        line.font = Font.systemFont(12);
        stack.addSpacer(2);
        return item;
      });
      if (clippedItems[i].length == 16) {
        let more = stack.addText("...")
      }
      stack.minimumScaleFactor = 1;
    }
    
    mainStack.addSpacer(2);
    
  }
  
  return w
}


// This is the main function to comb through my folder structure for every daily note- Comb each note for something that matches a regex "- [ ] xxx" with out without an ending date "YYYY-MM-DD"
async function findTasks(todayTitle) {
  
  //Set up file manager, finds the amount of Year folders in the Daily Notes folder and sets up storage arrays
  let fileManager = FileManager.iCloud();
  let years = await fileManager.listContents(fileManager.bookmarkedPath(root));
  let overdueTasks = [];
  let todayTasks = [];
  let tomorrowTasks = [];
  let nextTwoWeeksTasks = [];
  let longTermTasks = [];
  
  //Loops through each year folder
  for (let year of years) { 
    if (fileManager.isDirectory(fileManager.bookmarkedPath(root)+ "/" + year)) {
      
      //Finds each month folder in the year and loops through
      let months = await fileManager.listContents(fileManager.bookmarkedPath(root)+ "/" + year);
      for (let month of months) {
        
        //If month is a folder of years - search
        if (fileManager.isDirectory(fileManager.bookmarkedPath(root)+ "/" + year + "/" + month)) {
          let files = await fileManager.listContents(fileManager.bookmarkedPath(root)+ "/" + year + "/" + month);
          for (let file of files) {
            
            let fileContents = await fileManager.readString(fileManager.bookmarkedPath(root)+ "/" + year + "/" + month + "/" + file);
            let lines = fileContents.split("\n");

            for (let line of lines) {
              //Regexp to filter tasks and make a match that has [Full task, Task name, Due date, Task Name if no due date]
              let taskRegex = new RegExp("- \\[ \\] (.*) (\\d\\d\\d\\d-\\d\\d-\\d\\d)|- \\[ \\] (.+)");
              let match = line.match(taskRegex);
              
              
              //If found a task
              if (match) {
                //If Task has a due date
                if (match[2]) {
                  
                  let matchName = match[1].trimRight("📅");
                  let date = Date.parse(match[2]);
                  date = parseInt(date);
                  let dateToday = Date.parse(todayTitle);
                  dateToday = parseInt(dateToday);
                  let tomorrow = dateToday+86400000;
                  let twoWeeks = dateToday+86400000*14;
									
                  //sort task to array based on due date
                  if (date == dateToday) {
                    todayTasks.push(matchName);
                    
                  } else if (date < dateToday) {
                    overdueTasks.push(matchName);
                    
                  } else if (date <= tomorrow) {
                    tomorrowTasks.push(matchName);
                    
                  } else if (date <= twoWeeks) {
                    //Add date to these - I would like to sort them too but I cant be bothered
                  	let dateRegex = new RegExp("(\\d\\d\\d\\d-\\d\\d-)(\\d\\d)")
                    let dueDay = match[2].replace(dateRegex, "$2");
                    nextTwoWeeksTasks.push(matchName + " " + dueDay);
                    
                  }
                  
                } else {
                  let matchName = match[3].trimRight("📅");
                  longTermTasks.push(matchName)
                }

              }
            }
          }
        }
      }
    }
  }
	let tasks = [overdueTasks, todayTasks, tomorrowTasks, nextTwoWeeksTasks, longTermTasks];
  return tasks;
}

//My Root Folder
const root = "Daily Notes";
//get today in format YYYY-MM-DD
let today = new Date();
let todayTitle = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`

let items = await findTasks(todayTitle);
let widget = createWidget(items, todayTitle);

if (config.runsInWidget) {
  Script.setWidget(widget);
  Script.complete();
} else {
  widget.presentExtraLarge();
}