(v1) Migrating from Obsidian-Tasks

Hi

Thanks a lot for all the efforts to create this task features. I am having just one issue: How can I actually use it? :slight_smile:

Where do I need to put that script and how is it to be used?

Thanks a lot in advance and kind regards,

P.

Has anyone tried to do this in v2?

Not exactly this but I’ve been using a very customized version of this for a while. I use some special SB tags for task statuses and priorities - since I use it in contexts other than task as well. Other than that, I have all the projects under Projects/ directory in the space and all people under People/ directory. So that makes my space-lua something like:

taskmgmt = taskmgmt or {}
taskmgmt.valid_priorities = {"P0", "P1", "P2"}
taskmgmt.valid_statuses = {"TODO", "IN-PROGRESS", "PAUSED", "IN-REVIEW", "DONE", "CANCELED"}

function parseTaskAttributes(task, date)
  local taskattrs = {}
  local sev = ""
  local deadline = task.deadline or os.date("%Y-%m-%d")
  local diff = dateDiff(date, deadline)
  if task.done == true then sev = " βœ…"
  elseif diff < -4 then sev = " πŸ”₯"
  elseif diff >= -4 and diff < 0 then sev = " ‼️"
  elseif diff >= 0 and diff < 2 then sev = " πŸ”΄"
  elseif diff >= 2 and diff < 4 then sev = " 🟑"
  else sev = " 🟒"
  end
  taskattrs["severity"] = sev
  local _, pri = table.find(task.tags, function(tag) return  table.includes(taskmgmt.valid_priorities, tag) end)
  taskattrs["priority"] = pri and "#" .. pri or "-"
  local _, st = table.find(task.tags, function(tag) return table.includes(taskmgmt.valid_statuses, tag) end)
  taskattrs["status"] = st and "#" .. st or "-"
  local proj = string.match(task.name, "%[%[Projects/.*%]%]")
  taskattrs["project"] = proj or ""
  local as = ""
  for assignee in string.gmatch(task.name, "%[%[People/.-%]%]") do
    as = as .. assignee .. " "
  end
  taskattrs["assignees"] = as
  local completed = string.match(task.name, "βœ…(%d+%-%d+%-%d+)")
  taskattrs["completed"] = completed
  local created = string.match(task.name, "βž•(%d+%-%d+%-%d+)")
  taskattrs["created"] = created
  local name = string.gsub(task.name, "%[%[.*%]%]", "")
  name = string.gsub(name, "βœ…(%d+%-%d+%-%d+)", "")
  name = string.gsub(name, "βž•(%d+%-%d+%-%d+)", "")
  taskattrs["taskname"] = name
  return taskattrs
end

local mt = { 
  __index = function(self, attr) 
    if attr == "extraAttrs" then 
      return parseTaskAttributes(self, os.date("%Y-%m-%d"))
    end 
  end 
}
index.defineTag { name = "task", metatable = mt }

function markTaskStatus(status)
  local line = editor.getCurrentLine()
  local newline = line.text
  for i, v in ipairs(taskmgmt.valid_statuses) do
    if string.find(newline, v) then
      newline = string.gsub(newline, v, status)
      if string.find(newline, "[ ]") and status == "DONE" then
        newline = string.gsub(newline, "%[ %]", "[x]")
        newline = newline .. " βœ…" .. os.date("%Y-%m-%d")
      end
      if string.find(newline, "[x]", 0, true) and status ~= "DONE" then
        newline = string.gsub(newline, "%[x%]", "[ ]")
        newline = string.gsub(newline, "βœ…(%d+%-%d+%-%d+)", "")
      end
      break
    end
  end
  editor.replaceRange(line.from, line.to, newline)
end

function markTaskPriority(priority)
  local line = editor.getCurrentLine()
  local newline = line.text
  for i, v in ipairs(taskmgmt.valid_priorities) do
    if string.find(newline, v) then
      newline = string.gsub(newline, v, status)
    end
  end
  editor.replaceRange(line.from, line.to, newline)
end

for i, v in ipairs(taskmgmt.valid_statuses) do
  slashcommand.define {
    name = v,
    run = function()
      markTaskStatus(v)
    end
  }
end

for i, v in ipairs(taskmgmt.valid_priorities) do
  slashcommand.define {
    name = v,
    run = function()
      markTaskStatus(v)
    end
  }
end

And with some custom styling for the special tags:

/* TODO tag */
.sb-hashtag[data-tag-name="TODO"] {
  background: #ff8080 !important;
  color: #333333 !important;
  font-weight: bolder;
}

.sb-hashtag[data-tag-name="TODO"]::before {
  content: "πŸ“Œ ";
}

/* IN-PROGRESS tag */
.sb-hashtag[data-tag-name="IN-PROGRESS"] {
  background: #ffcc77 !important;
  color: #333333 !important;
  font-weight: bolder;
}

.sb-hashtag[data-tag-name="IN-PROGRESS"]::before {
  content: "🚧 ";
}

/* IN-REVIEW tag */
.sb-hashtag[data-tag-name="IN-REVIEW"] {
  background: #bf88bf !important;
  color: #333333 !important;
  font-weight: bolder;
}

.sb-hashtag[data-tag-name="IN-REVIEW"]::before {
  content: "πŸŸͺ ";
}

/* PAUSED tag */
.sb-hashtag[data-tag-name="PAUSED"] {
  background: #ffa742 !important;
  color: #333333 !important;
  font-weight: bolder;
}

.sb-hashtag[data-tag-name="PAUSED"]::before {
  content: "🟧 ";
}

/* DONE tag */
.sb-hashtag[data-tag-name="DONE"] {
  background: #b0ffa9 !important;
  color: #333333 !important;
  font-weight: bolder;
}

.sb-hashtag[data-tag-name="DONE"]::before {
  content: "βœ… ";
}

/* CANCELED tag */
.sb-hashtag[data-tag-name="CANCELED"] {
  background: #b3d9ff !important;
  color: #333333 !important;
  font-weight: bolder;
}

.sb-hashtag[data-tag-name="CANCELED"]::before {
  content: "β›” ";
}

/* P0 tag */
.sb-hashtag[data-tag-name="P0"] {
  background: #ffb3b3 !important;
  color: #333333 !important;
  font-weight: bolder;
}

.sb-hashtag[data-tag-name="P0"]::before {
  content: "πŸ”΄ ";
}

/* P1 tag */
.sb-hashtag[data-tag-name="P1"] {
  background: #ffdbb3 !important;
  color: #333333 !important;
  font-weight: bolder;
}

.sb-hashtag[data-tag-name="P1"]::before {
  content: "🟠 ";
}

/* P2 */
.sb-hashtag[data-tag-name="P2"] {
  background: #ffe3b3 !important;
  color: #333333 !important;
  font-weight: bolder;
}

.sb-hashtag[data-tag-name="P2"]::before {
  content: "🟑 ";
}

This sort of fits my use cases mostly I can write a task of the form:

* [ ] Test task 1 [[People/Person-1]] [[Projects/Project-blah]] πŸ“…2025-11-14 #TODO #P1 

and get all the relevant fields extracted out. This is not a 100 percent replacement, but thought I’d share it if anyone wants to improve on it.

Thanks for sharing! I am mostly wondering about the reoccurring tasks piece. I am looking for a way to do them in v2.

Hi! I was also struggling to migrate without the ability to create recurring tasks

I was tinkering with space-lua for some time and got this implementation:
It’s fairly simple and (I hope) human-readable

It just listens for state changes of tasks (it seems that event is activated only when a task changes in the current buffer).
If the task is marked as done, it recreates the task with a new deadline.
Inside the recur, you can specify X days, and the new deadline of the task will be X days later.

DAY = 24 * 60 * 60

function time_from_string(date)
	local year, month, day = string.match(date, "(%d+)%-(%d+)%-(%d+)")
	return os.time({ year = tonumber(year), month = tonumber(month), day = tonumber(day) })
end

function recur_to_new_deadline(deadline, recur)
	return os.date(date.date_format, time_from_string(deadline) + (recur * DAY))
end


event.listen({
	name = "task:stateChange",
	run = function(e)
		if e.data.newState == "x" then
			local ref = editor.getCurrentPage() .. "@" .. e.data.from - 2
			local task = index.getObjectByRef(editor.getCurrentPage(), "task", ref)
			if (not (type(task.recur) == "number")) or (not (type(task.deadline)) == "string") then
                print("Recur is not a number, or deadline is not a string!")
				return
			end
            
			local new_deadline = recur_to_new_deadline(task.deadline, task.recur)
			local new_task_text = "[ ] "
				.. task.name
				.. " [deadline:\"" .. new_deadline .. "\"]"
				.. " [recur:" .. task.recur .. "]"
			editor.replaceRange(e.data.from, e.data.to, new_task_text)
		end
	end,
})

So this task

  • test [deadline:β€œ2026-01-11”] [recur:7]
    when "completed "
  • test [deadline:β€œ2026-01-11”] [recur:7]
    will become this one
  • test [deadline:β€œ2026-01-18”] [recur:7]

I also added a simple slash commands to make it easier to input them - just start typing β€œ/de” and it suggests deadline, same thing with recur. Also to specify day inside deadline i use /today command, at least as start point.

slashCommand.define {
  name = "deadline",
  run = function()
    editor.insertAtCursor("[deadline: ]")
    local currentPosition = editor.getCursor()
    editor.moveCursor(currentPosition-1)
  end
}

slashCommand.define {
  name = "recur",
  run = function()
    editor.insertAtCursor("[recur: ]")
    local currentPosition = editor.getCursor()
    editor.moveCursor(currentPosition-1)
  end
}

I was also able to make the same concept work with a whole index of tasks, but that implementation was a little dirtier and not so easy to recommend.
If I find a time to make a realization with the index cleaner, I will share it here, probably.

By the way, share your wishes for that thingy; maybe I will be able to make them real :grinning_face_with_smiling_eyes: