Quickly Toggle Task Completion Status and Add Date

Demo

250416_快速切换任务完成状态并添加日期-img-250416_133047

Description

When using the default “Toggle To-Do” command, it is not possible to add a completion date in conjunction with the Tasks plugin.

Therefore, this TP script was written to add a completion date while toggling the completion status.
It also supports:

  1. Converting plain text into task items
  2. Toggling between completed/uncompleted status
  3. Toggling between empty lists and empty tasks

Simply place the following TP script document into your library and register it as a shortcut in the Templater plugin (I directly overwrote the default Ctrl+L key).

TP Script: Toggle Task Status

<%*
// Get the editor instance
const editor = app.workspace.activeEditor.editor;

// Get the current line
const cursor = editor.getCursor();
const line = editor.getLine(cursor.line);

// 1. Convert plain text or plain list into an uncompleted task
if (!/^[\s]*- \[[ x-]\]/.test(line)) {
    // Convert plain text or plain list into an uncompleted task
    let newLine;
    if (line.startsWith('- ') || line.trim().startsWith('- ')) {
        // If it's a plain list item, insert the task marker after the dash
        newLine = line.replace(/^(\s*- )/, '$1[ ] ');
    } else {
        // If it's plain text, convert it into a task, preserving the original indentation
        const indentation = line.match(/^\s*/)[0];  // Get the leading whitespace
        newLine = `${indentation}- [ ] ${line.trim()}`;
    }
    editor.replaceRange(newLine, { line: cursor.line, ch: 0 }, { line: cursor.line, ch: line.length });
    // Move the cursor to the end of the line
    editor.setCursor({ line: cursor.line, ch: newLine.length });
    return;
}

// ! Check the current task status
let newLine = line;
if (line.includes('[ ]')) {
    // If it's in an uncompleted state, first check if there is content
    if (line.replace('- [ ]', '').trim().length > 0) {
        // Mark as completed and add the date
        const today = new Date();
        const dateStr = today.toISOString().split('T')[0].replace(/-/g, '-');
        newLine = newLine.replace(/\[ \]/, '[x]');
        newLine = newLine + ` ✅ ${dateStr}`; // Add the date here, removing trailing spaces
    } 
    // Otherwise, switch back to a plain list
    else {
        // If there is no content, directly remove the task marker
        newLine = newLine.replace(/\[ \]/, '');
    }
} else if (line.includes('[x]')) {
    // If it's in a completed state, switch back to an uncompleted state
    newLine = newLine.replace(/\[[x]\]/, '[ ]');
    newLine = newLine.replace(/\s*✅\s*\d{4}-\d{2}-\d{2}\s*$/, '');
} else {
    // If it's in another state, switch to an uncompleted state
    newLine = newLine.replace(/\[[ x]\]/, '[ ]');
}

// Replace the current line—note the trimmed length, otherwise it may result in an extra space
editor.replaceRange(
    newLine.trimEnd(),
    { line: cursor.line, ch: 0 },
    { line: cursor.line, ch: line.trimEnd().length }
);

-%>

Direct download: ToggleTask 切换任务状态.md

4 Likes

Just made an update so that the task could cycle between “Unfinished/Done/Cancel”:

7b49497a-9755-41ee-9023-27efabd3ddf8

<%*
// 获取编辑器实例
const editor = app.workspace.activeEditor.editor;

// 是否循环切换任务状态
const isCycling = true;

const cycleMapping = {
    '[ ]': '[x]',
    '[x]': '[-]',
    '[-]': '[ ]'
}

// 获取当前行
const cursor = editor.getCursor();
const line = editor.getLine(cursor.line);

let newLine = line;

// 1. 将普通文本或普通列表转换成未完成的任务
if (!/^[\s]*- \[[ x-]\]/.test(line)) {
    // 将普通文本或普通列表转换为未完成的任务
    if (line.endsWith('- ')) {
        newLine = line.replace('- ', '- [ ] ');
    } else if (line.startsWith('- ') || line.trim().startsWith('- ')) {
        // 如果是普通列表项,在破折号后插入任务标记
        newLine = line.replace(/^(\s*- )/, '$1[ ] ');
    } else {
        // 如果是普通文本,转换为任务,保留原有缩进
        const indentation = line.match(/^\s*/)[0];  // 获取开头的空白字符
        newLine = `${indentation}- [ ] ${line.trim()}`;
    }
    editor.replaceRange(newLine, { line: cursor.line, ch: 0 }, { line: cursor.line, ch: line.length });
    // 将光标移动到行末尾
    editor.setCursor({ line: cursor.line, ch: newLine.length });
    return;
}

// ! 检查当前任务状态

// 空任务的情况——切换回列表
if (line.endsWith('- [ ] ')) {
    newLine = line.replace('- [ ] ', '- ');
    editor.replaceRange(
        newLine.trimEnd(),
        { line: cursor.line, ch: 0 },
        { line: cursor.line, ch: line.trimEnd().length }
    );
    return;
}

const today = moment().format('YYYY-MM-DD');

if (!isCycling) {
    if (line.includes('[ ]')) {
        // 标记为完成状态并添加日期
        newLine = newLine.replace(/\[ \]/, '[x]');
        newLine = newLine + ` ✅ ${today}`; // 这里添加日期,去掉行末空格
    } else if (line.includes('[x]')) {
        // 如果是完成状态,切换回未完成状态
        newLine = newLine.replace(/\[[x]\]/, '[ ]');
        newLine = newLine.replace(/\s*✅\s*\d{4}-\d{2}-\d{2}\s*$/, '');
    } else {
        // 如果是其他状态,切换为未完成状态
        newLine = newLine.replace(/\[[ x]\]/, '[ ]');
    }
} else {
    const currentStatus = line.match(/- \[.\]/)[0]?.replace('- ', '');

    console.log(`currentStatus: ${currentStatus}`);

    if (!currentStatus) {
        newLine = '- [ ]' + newLine;
    }

    const nextStatus = cycleMapping[currentStatus];

    // 移除已有的 ✅xxxx-xx-xx 或 ❌xxxx-xx-xx
    newLine = line.replace(/ (✅|❌) \d{4}-\d{2}-\d{2}/g, '');

    if (nextStatus === '[x]') {
        // 添加完成标记和日期
        newLine = newLine.replace(/- \[.\]/, `- [x]`) + ` ✅ ${today}`;
    } else if (nextStatus === '[-]') {
        // 添加否决标记和日期
        newLine = newLine.replace(/- \[.\]/, `- [-]`) + ` ❌ ${today}`;
    } else {
        // 仅切换状态,不加日期
        newLine = newLine.replace(/- \[.\]/, `- ${nextStatus}`);
    }
}

// console.log(`>>${newLine}<<`);

// 替换当前行——注意是 trim 后的长度,不然可能导致多一个空格
editor.replaceRange(
    newLine.trimEnd(),
    { line: cursor.line, ch: 0 },
    { line: cursor.line, ch: line.trimEnd().length }
);

// 然后运行一下重新排序的任务
// window.open("obsidian://advanced-uri?vault=Obsinote&commandid=templater-obsidian%253A_global%252Ftemplates%252Fcommand%252FtpRunner.md&template=ReOrder List 重排序列表");
// 这里是不是该用那个用户函数了捏…… 🤔

-%>