GTD with Obsidian: a ready-to-go GTD system with Task Sequencing, Quick-add template, Waiting-on, Someday/Maybe, and more

First of all, thank you very much for this much needed implementation of GTD in Obsidian!

When i tried to implement the tasks.js in my vault, i am getting a weird problem that when i check of the task from the dataview view , the next task in the sequence is not updated ; but if i check the task off manually by going to the original file containing that task then the dataview view of tasks.js is updated correctly…

This problem was SOLVED when i commented out the whole piece of code that was used to fetch the value of the headingLine variable? I believe that piece of code might have some problem that i am not knowledgeable enough to pin point.

PS. I did make so changes to the tasks.js file to make it suitable for my needs - the main changes being that i) i commented out the entire section on fetching tasks that are not linked to particular project ii) i added a folder path in the dv.pages() argument so that it will only look for tasks in that folder which is what i need.

My whole modified tasks.js file given below.

/*
 * ---------------------
 * CONFIGURATION OPTIONS
 * ---------------------
 */

// Add the folders, tags, and headings which should be excluded.
// Tasks found on pages with these tags or paths will not appear in the Tasks page.
// Headings will to exclude tasks that fall under a heading with this name.
const globalExclude = {
  folders: [
    'Utility'
  ],
  tags: [
    '#exclude-master-tasklist',
    '#completed'
  ],
  headings: [
    '🌱 Daily Habits'
  ]
}

// When displaying tasks from a project, tasks that fall under headings with these
// names won't have these headings displayed when showing the project info
const hideSubsectionName = [
  'Todo',
  'Tasks'
]

// If you are using the Tasks plugin, you can hide tasks which are scheduled in the future.
// They will have a date in the format ⏳ 2021-04-09
const hideFutureScheduledTasks = false

/*
 * ----------------------------
 * END OF CONFIGURATION OPTIONS
 * ----------------------------
 */

// Set up the variables to store the tasks and projects
const tasks = []
const noNextAction = []
const excludeItems = ['', ...globalExclude.tags]
globalExclude.folders.forEach(folder => excludeItems.push(`"${folder}"`))
const globalExcludeString = excludeItems.join(' AND -')

// Define groups for master tasklist page
const Groups = {
  Waiting: 0,
  Priority: 1,
  Normal: 2,
  Someday: 3
}

/**
 * Take a task and the page it's from, and return a formatted element for the tasks array
 * @param {*} task
 * @param {*} page
 * @returns {object}
 */
function generateTaskElement (task, page) {
  let group = Groups.Normal
  if (task.tags.includes('#someday')) {
    group = Groups.Someday
  } else if (task.tags.includes('#waiting-on')) {
    group = Groups.Waiting
  } else if (task.text.includes('🔼') || page.tags.includes('#🔼')) {
    group = Groups.Priority
  }

  // Get the created date of a task for sorting, either from the task text
  // in the format of ➕2024-04-20, or from the page metadata.
  let date
  const hasDate = task?.text?.match(/➕\s?(\d{4}-\d{2}-\d{2})/)
  if (hasDate) { // Task inline created date
    date = moment(hasDate[1].valueOf())
  } else if (page.created) { // Page `created` frontmatter property
    date = page.created?.ts || moment(page.created).valueOf()
  }
  date = date || page.ctime.ts

  return {
    task,
    date,
    group
  }
}

/*
 * Process projects
 */
dv.pages('(#active-project or "path/to/folder/with/my/tasks") ' + globalExcludeString)
  .where(project => !project.completed).file
  .forEach(project => {
    const sections = []
    if (!project.tasks.filter(t => !t.completed && t.text).length) {
      // There is no next action for this project
      noNextAction.push(project)
    } else {
      project.tasks
        .where(t => !t.completed && t.text)
        .forEach(task => {
          const sectionName = task.section.subpath || 'root'
          // Select only the first task from each section. This allows task sequencing.
          if ((!sections.includes(sectionName) && !sectionName.includes('exclude')) || sectionName.includes('🟰')) {
            sections.push(sectionName)
            let subSection = ''
            let headingLine = 1
            // if (
            //   // There is a subpath/heading
            //   task.section.subpath &&
            //   // And it's not one of our whitelisted headings which don't require a notated sub-section
            //   !hideSubsectionName.includes(sectionName) &&
            //   // And it doesn't match the project name
            //   sectionName !== project.name.replace(/\W/g, ' ')
            // ) {
            //   // Add it as a sub-section to the project name
            //   subSection = ` > ${sectionName}`
            //   // Find the line-position of the heading for this task
            //   const file = app.fileManager.vault.getAbstractFileByPath(project.path)
            //   const headings = app.metadataCache.getFileCache(file).headings
            //   const match = headings.find(x => x.heading === sectionName)
            //   headingLine = match ? match.position.start.line : task.line - 1
            // }
            // By adding this subtask, we can get a sub-line with the project and/or heading names
            task?.subtasks?.push({
              children: [],
              path: task.path,
              link: task.link,
              // line: headingLine, // We need this to be able to jump to the heading when clicked
              lineCount: task.lineCount,
              position: task.position,
              text: `🗃️ *${project.name}${subSection}*`
            })
            tasks.push(generateTaskElement(task, project))
          }
        })
    }
  })

/*
 * Process tasks

dv.pages('-#project' + globalExcludeString)
  .where(p => p.file.tasks.length && !p['kanban-plugin'] && !p['exclude_master_tasklist']).file
  .forEach(page => {
    page.tasks
      .where(t => {
        if (hideFutureScheduledTasks) {
          // Check for a scheduled date in the format of ⏳2024-04-20
          const scheduledDate = t?.text?.match(/⏳\s?(\d{4}-\d{2}-\d{2})/)
          if (scheduledDate) {
            // Check if the date is in the future
            if (moment(scheduledDate[1]) > moment()) {
              // Skip and move on to the next task
              return false
            }
          }
        }

        return t.text && // where the task has text (is not blank)
          !t.completed && // and not completed
          !t.tags.includes('#exclude') && // and not excluded
          (!t.header.subpath || !t.header.subpath.includes('exclude')) &&
          !globalExclude.headings.includes(t.header.subpath) // and the heading is not excluded
      })
      .forEach(task => tasks.push(generateTaskElement(task, page)))
  })
 */



// Sort tasks into groups, then ascending by created time
tasks.sort((a, b) => a.group - b.group || a.date - b.date)

/**
 * Output a formatted task list
 * @param {number|null} group - Filter for tasks in a particular group, or null for all tasks
 * @param {string|null} header - The text header for the task list
 */
function taskList (group, header) {
  const list = isNaN(group) ? tasks : tasks.filter(x => x.group === group)
  if (list.length) {
    if (header) dv.header(2, header)
    dv.taskList(list.map(x => x.task), false)
  }
}

// Projects without next action


// Output the task list
taskList(Groups.Priority, '🔼 Priority',)
taskList(Groups.Normal, '✅ Next actions')
taskList(Groups.Waiting, '⏳ Waiting on...')
taskList(Groups.Someday, '💤 Someday')

if (noNextAction.length) {
  dv.header(2, '🚩 Projects without next actions')
  dv.list(noNextAction.map(p => p.link))
}