В статье будет показан прототип системы дел, который реализован в Obsidian. Система в основном будет базироваться на идеях GTD.
Задачи будут создаваться в дневнике/журнале и агрегироваться в отдельных заметках с использованием плагина Tasks.
Система будет адаптирована для телефонов.
Статья написана для продвинутых юзеров Obsidian. Новичкам, конечно, с ней будет тяжеловато разобраться.
Структура статьи (оглавление)
Введение
Система является прототипом, т.к. она реализуется в среде Obsidian. Obsidian – это софт, который не специализирован под задачи.
Хотя если говорить глобально, то Obsidian вообще ни под что не специализирован.
Можно ещё сказать, что статья не про «как сделать систему дел в Obsidian», а про «смотрите, я тут немного поисследовал разные возможности и в процессе придумал вот такие-то штуки».
Сама статья устроена по такой логике: сначала я покажу основную идеи и приёмы, потом обобщу всё в одну панель, далее разные дополнения и финты, затем пример, в конце преимущества и недостатки.
Стоит отметить, что в статье будут часто встречаться куски кода. Из меня такой себе кодер. Если же вы напротив опытный программист и у вас вдруг начнут вытекать глаза, то соберите их обратно в глазницы и предложите более красивый, стройный и оптимальный код в комментариях.
Итак, начнём.
Общая структура задачи
Вся идея системы дел в Obsidian будет закручена вокруг мысли, что любую задачу можно записать следующим образом:
- [ ] prefixes content time/date
-
prefixes
– определяют тип, категорию и прочие параметры задачи -
content
– текст задачи -
time/date
– время и дата выполнения задачи
Приведу сразу пример задачи:
- [ ] #task/waiting_for #category/household Попросить что-то выполнить у [[Титус|Титуса]] ? 12:00 ? 2024-03-14
-
#task/waiting_for #category/household
– префикс -
Попросить что-то выполнить у [[Титус|Титуса]]
– контент -
? 12:00 ? 2024-03-14
– время и дата
В данной статье в качестве префикса будут использоваться именно теги.
Префиксы – тип задачи и категория
В качестве типов задач я буду частично использовать подход из GTD.
Так у меня будут следующие типы задач:
Тип задачи |
Описание |
---|---|
? inbox |
Инбокс |
? next_action |
Метка, говорящая «делай именно эту задачу» |
1️⃣ one-off |
Задача, которую можно сделать за один подход |
? multistep |
Проектная задача (имеющая подзадачи) |
? waiting_for |
Делегированная задача |
? regular |
Повторяющаяся задача |
? idea |
Идея или мимолётная заметка |
? reference |
Ссылка на ресурс, который нужно изучить и обработать |
? someday |
Когда-нибудь потом |
Я сразу добавил эмодзи. Скоро вы поймете зачем они нужны.
Тип задачи будет задаваться вложенным тегом #task/
.
типы задач – пример
- [ ] #task/idea Сделать платный vault в Obsidian - [ ] #task/one-off Сходить к ветеринару - [ ] #task/... ...
Теперь введём понятие «категории». В силу того, что данную систему я накрутил в своей основной базе, то категории у меня обычно выступают как сферы знаний. Для данной статьи я выберу всего две таких:
Категория |
Описание |
---|---|
? knowledge_base |
Отвечает за всё, что связано со знаниями, заметками и ресёрчем |
? health |
Отвечает за исследование общей проблемы здоровья и за моё здоровье в частности |
Категория также будет задаваться вложенным тегом #category/
.
категории задач – пример
- [ ] #category/knowledge_base Исследовать проблему внедрения системы дел в базу знаний - [ ] #category/health Найти лекарство от рака
Возможно вы уже заметили, что префиксы занимают многовато места. Скоро я это исправлю.
Почему теги
Немного подробнее обсудим почему я выбрал теги в качестве префиксов. Если кратко, то потому что теги плюс-минус адекватно поддерживается самим Obsidian и плагином Tasks.
Несомненно можно было в качестве префиксов выбрать, например, ссылки на категории. Т.е. сделать что-то такое:
префикс в виде категории
- [ ] [[health|?]] Разработать лекарство от рака
Это бы решило проблему длинного префикса, но тогда бы появилась проблема поиска задач (попробуйте сейчас написать такой поисковый запрос, который бы точным образом находил все незавершённые задачи, содержащие ссылку на [[health]]
*). Вдобавок запросы tasks стали бы весьма замороченными.
*Не уверен, но, по-моему, поисковый запрос должен как-то так выглядеть /- \[ \] .*?\[\[health(?:\|[^]]*)?\]\]/
.
Можно ещё в качестве префикса выбрать просто эмодзи. Т.е. вот так делать:
- [ ] ? Разработать лекарство от рака
Это бы также решило проблему длины префикса. Поиск по задачам стал бы проще, но такой подход сложно интегрировать с основной базой знаний. Т.е. будет несколько затруднительно одновременно найти, например, все внутренние и внешние источники (references), которые принадлежали бы к одной категории и содержали одно ключевое слово. Вдобавок на телефоне эмодзи вводить тоже не очень удобно.
Короче говоря, в каждом способе были бы свои проблемы, которые пришлось бы как-то решать. В тегах этих проблем оказалось меньше, поэтому их я и выбрал.
Кстати есть вариант с символами внутри markdown-задачи:
- [h] Разработать лекарство от рака
Однако этот способ совсем плох, ибо он нарушает синтаксис markdown, его не любят некоторые темы и ещё этот метод одномерный (можно задать только тип задачи). Вдобавок Tasks хоть и провайдит такой метод маркировки задач, но сам при этом пока что не шибко полноценно его поддерживает.
Создание и обзор задач
Задачи я буду создавать в периодических заметках. Этот метод хорош тем, что не нужно ломать себе голову с тем куда пристроить задачи. Просто создаёшь дневную заметку: пишешь дневник/журнал, по ходу накидываешь задачи.
Добавление задачи
Начнём с базового утверждения.
Чтобы задача попала в систему дел, у неё должен появиться префикс.
Префикс можно прописать вручную. Однако это медленно, а на телефоне так вообще работать с текстом и что-то там прописывать не очень удобно.
Чтобы ускорить процесс создания задач, я написал довольно мудрёный шаблон для Templater.
Функционал этого шаблона сводится к следующему:
-
Шаблон сделает из текста под курсором задачу. Префиксы же можно будет выбрать из выпадающих списков
Создание задачи
-
Шаблон может менять префиксы у задачи, которая находится под курсором
Изменение префикса
-
Шаблон может создавать задачи и менять префиксы массово
массовое изменение задач
По мне так быстрее способа добавления задач и не придумаешь, хех. Ещё этот шаблон будет чертовски удобен на телефоне.
шаблон для создание задачи
<%* // get contents of line under cursor const cmEditorAct = this.app.workspace.activeLeaf.view.editor; const currentCursor = cmEditorAct.getCursor(); const currentLine = currentCursor.line; const originLineContents = cmEditorAct.getLine(currentLine); // or get selected contents const lineSelection = await tp.file.selection() let lineContents = "" let isSelection = "" if (lineSelection.length >= originLineContents.length) { lineContents = lineSelection; isSelection = true; } else { lineContents = originLineContents; isSelection = false; } // set tags const taskType = [ "? inbox", "? idea", "1️⃣ one-off", "? multistep", "? next_action", "? reference", "? waiting_for", "? someday", "? regular", ]; const taskTags = taskType.map(function(value) { return "#task/" + value.replace(/^[^\s]*\s*/, ""); }); // tag selection let taskTag = await tp.system.suggester( taskType, taskTags, false, "Task type", ); if (taskTag == null) { taskTag = ""; } // set category const categories = [ "? health", "?️ knowledge_base", ]; const categoryTags = categories.map(function(value) { return "#category/" + value.replace(/^[^\s]*\s*/, ""); }); // category selection let categoryTag = await tp.system.suggester( categories, categoryTags, false, "Category", ); if (categoryTag == null) { categoryTag = ""; } // temporary deletion of task mark const taskRegex = /- \[ \] /g; lineContents = lineContents.replace(taskRegex, ""); // converting urls to markdown links const linkRegex = /(?![^[]*\])(https?:\/\/[^\s]+)|\[(.*?)\]\((https?:\/\/[^\s]+)\)/g; lineContents = lineContents .replace(linkRegex, (match, p1, p2, p3) => { if (p2 && p3) { return `[${p2}](${p3})`; } else { const siteName = p1.match(/(?:https?:\/\/)?(?:w{3}\.)?([a-zA-Z0-9-]+)\.[a-zA-Z]{2,}(?:\.[a-zA-Z]{2,})?(?:\/\S*)?/,)[1]; return `[${siteName}](${match})`; } }) // add or modify the tag/category // const tagRegex = /#[\w-]+(\/[\w-]+)+/; const tagRegex = new RegExp(taskTags.join("|"), "g"); const categoryRegex = new RegExp(categoryTags.join("|"), "g"); let finalTask = taskTag let finalCategory = categoryTag let paragraphs = lineContents.split(/\n/); let tabulation for (let i = 0; i < paragraphs.length; i++) { if (paragraphs[i].trim() !== "") { if (taskTag == "") { finalTask = (paragraphs[i].match(tagRegex) || [""])[0]; } if (categoryTag == "") { finalCategory = (paragraphs[i].match(categoryRegex) || [""])[0]; } tabulation = (/\t/.test(paragraphs[i])) ? paragraphs[i].match(/\t/g).join("") : ""; paragraphs[i] = paragraphs[i].replace(tagRegex, "").replace(categoryRegex, "").trim() paragraphs[i] = tabulation + "- [ ] " + finalTask + " " + finalCategory + " " + paragraphs[i]; } } lineContents = paragraphs.join("\n"); // remove extra spaces lineContents = lineContents.replace(/ +/g, " "); // line modification if (isSelection) { tR += lineContents } else { cmEditorAct.replaceRange( lineContents, { line: currentLine, ch: 0 }, { line: currentLine, ch: originLineContents.length }, ); } %>
Задачи на сегодня и на последующие дни
Если вы пробовали плагин Tasks, то наверняка у вас эти запросы уже есть. Я лишь просто покажу свой вариант таких запросов.
запросы на сегодня
Due
due today hide task count hide due date
Scheduled
scheduled today hide task count hide scheduled date
Overdue
(due before today) OR (scheduled before today) not done hide task count
запросы на последующие дни
Tomorrow
not done (due tomorrow) OR (scheduled tomorrow) group by function reverse task.scheduled.format("%%%%") ? "⌛ Scheduled:" : "? Due:" hide task count hide scheduled date hide due date
Upcoming (after tomorrow)
not done (due after tomorrow) OR (scheduled after tomorrow) group by function task.happens.format("YYYY-MM-DD") hide task count hide due date hide recurrence rule
Группирование задач
Теперь перейдём к самом интересному. К группированию задач.
Общая идея
В типах задач, мы можем сгруппировать по категориям.
В категориях по типам.
В этом и заключается основная идея группировки.
В силу того, что мы уже и так промаркировали задачи, нам остаётся только сделать правильные запросы Tasks.
Группирование в статусах по категориям
Все запросы для статусов будут выглядеть одинаково, за исключением одной строки.
Пример запроса выглядит вот так (для задач со статусом ? next_action
):
not done tags include #task/next_action group by function task.tags.filter( (tag) => tag.includes("#category/") ).map( (tag) => tag.split('/')[1] ? tag.split('/').slice(1, 2) : '') sort by function task.happens.format("YYYY-MM-DD") hide task count hide tags
Разберём построчно:
-
not done
-
Отобрать незавершённые задачи
-
-
tags include #task/next_action
-
Отобрать задачи, которые содержат тег
#task/next_action
-
Эту строку нужно будет менять в зависимости от статуса
-
-
group by function task.tags.filter( (tag) => tag.includes("#category/") ).map( (tag) => tag.split('/')[1] ? tag.split('/').slice(1, 2) : '')
-
group by function task.tags
-
сгруппировать по тегам
-
-
.filter( (tag) => tag.includes("#category/") )
-
но не по всем, а только по тем, которые содержат
#category/
-
-
.map( (tag) => tag.split('/')[1] ? tag.split('/').slice(1, 2) : '')
-
сгруппировать только по той части, что идёт после
#category/
-
это будет название самой категории (например,
? knowledge_base
)
-
-
-
sort by function task.happens.format("YYYY-MM-DD")
-
отсортировать задачи внутри групп так, чтобы сначала показывались более близкие по времени
-
-
hide task count
-
скрыть количество задач
-
-
hide tags
-
скрыть теги в задачах
-
зачем их повторять, если мы и так по ним группируем?
-
Возможно я чутка перемудрил с функцией группировки. Если вы знаете как написать проще, то расскажите об этом в комментариях.
пример вывода запроса для статуса #task/next_action
CSS-сниппет, чтобы поменять обратную ссылку на иконку ?
.
Для всех остальных статусов нужно будет поменять только строку с tags include
и результат будет такой же.
Группирование в категориях по статусам
Логика группирующего запроса будет точно такой же.
Сам запрос выглядит вот так:
not done tags include #category/knowledge_base group by function task.tags.filter( (tag) => tag.includes("#task/") ).map( (tag) => tag.split('/')[1] ? tag.split('/').slice(1, 2) : '') hide task count hide tags
Единственная особенность, что теперь мы сначала отберём задачи с категорией, а потом уже сгруппируем по статусам.
пример вывода запроса для категории #category/knowledge_base
Порядок отображения групп
На мой взгляд, статусы лучше отобразить в порядке их значимости.
В документации Tasks, к сожалению, нет красивого способа задания порядка для отображаемых групп. При этом классические способы сортировки у меня почему-то не сработали. В общем пришлось эту проблему решить весьма громоздкими способами.
Решений будет два:
1. Ручной метод
not done tags include category/knowledge_base group by function \ if (task.tags.includes("#task/inbox")) return "%%01%% ? [[_inbox|Inbox]]"; \ if (task.tags.includes("#task/next_action")) return "%%02%% ? Next Action"; \ if (task.tags.includes("#task/one-off")) return "%%03%% 1️⃣ One-off"; \ if (task.tags.includes("#task/multistep")) return "%%04%% ? Multistep"; \ if (task.tags.includes("#task/waiting_for")) return "%%05%% ? Waiting for"; \ if (task.tags.includes("#task/regular")) return "%%06%% ? Regular"; \ if (task.tags.includes("#task/someday")) return "%%07%% ? Someday"; \ if (task.tags.includes("#task/idea")) return "%%08%% ? Idea"; \ if (task.tags.includes("#task/reference")) return "%%09%% ? Reference"; \ return "%%00%% Without status"; hide task count hide tags
2. Слегка автоматизированный метод
not done tags include category/knowledge_base group by function \ const order = ["#task/inbox", \ "#task/next_action", \ "#task/one-off", \ "#task/multistep", \ "#task/waiting_for", \ "#task/regular", \ "#task/someday", \ "#task/idea", \ "#task/reference"]; \ for (let i = 0; i < order.length; i++) { \ if (task.tags.includes(order[i])) { return "%%" + i + "%% " + order[i].replace("#task/", "") } \ }; \ return "%%_%% Without status"; hide task count hide tags
Результат вывода ручного метода:
вывод запроса для категории
Проблемка с вложенными задачами
Как понять, что задача является подзадачей? В task-менеджерах это всегда так или иначе обозначено и поддерживается это автоматически.
В нашем же случае есть по сути два варианта как показать, что задача является подзадачей.
-
Указать это явно
- [ ] #task/multistep Основная задача - [ ] ⤵️ Подзадача 1 - [ ] ⤵️ Подзадача 2
-
Воспользоваться функцией связанных задач
- [ ] #task/multistep Основная задача ? abcdef - [ ] Подзадача 1 ⛔ abcdef - [ ] Подзадача 2 ⛔ abcdef
В итоге, конечно, суть не шибко отличается – что там определённые эмодзи в задачи, что там, хах.
Кстати, функция связанных задач довольно странно работает. Чтобы она корректно отрабатывала, нужно указать связь именно так, как я показал на скрине, а не, так как объясняется в документации.
Вообще… Я думаю, что на эту проблему можно отчасти забить, ибо она решается автоматически, если рабочий процесс подразумевает обзор задач. Я имею в виду то, что если подзадача была выполнена и при этом проигнорировано, что она является часть какого-то проекта, то ничего страшного не случится. На следующем обзоре так или иначе в проект допишутся и промаркируются новые задачи.
Если вы знаете другие способы указания, что задача является подзадачей, то напишите о них в комментариях.
Изменение отображения тегов
Вы могли заметить, что вложенные теги занимают много места. Несомненно является плюсом, что по длинному названию тегов можно быстро понять, что они значат. Но с точки зрения плотности информации, мы имеем скорее больше шума, чем пользы.
Решить проблему длины тегов можно несколькими способами.
-
1-ый вариант – сделать сами теги короче
Вместо
- [ ] #task/multistep ...
можно использовать сокращения (так делает, например, этот юзер)
- [ ] #t/m ...
или вообще сделать одиночный тег
- [ ] #multi ... или - [ ] #m или - [ ] #?️ ...
Мне эти варианты не нравятся, т.к. подобные сокращения плохо считываются в принципе. Одиночные теги же мне не нравятся просто из-за своей неупорядоченности.
-
2-ой вариант – изменить отображение определённых тегов
Идея в том, чтобы сохранить нормальные слова, которые будут поддерживать считываемую структуру, но при этом уплотнить визуально информацию.
Для этого можно заюзать следующий css-хак
tags.css
body { --color-of-statuses: rgba(162, 93, 53, 0.1); --color-of-categories: rgba(0, 255, 255, 0.1); } .tag[href="#task/multistep"], .cm-tag-taskmultistep { font-size: 0px; padding: 0; } .tag[href="#task/multistep"]:after, .cm-tag-taskmultistep:after, { font-size: var(--font-text-size); } .cm-active .tag[href="#task/multistep"], .cm-active .cm-tag-taskmultistep, .cm-hashtag-begin.cm-tag-taskmultistep:after { border-radius: 3px; background-color: var(--color-of-statuses); content: "?"; }
Суть его сводится к тому, чтобы #task/multistep
отобразить как ?️
.
Если этот хак применить ко всем тегам статусов и категорий, то задачи начнут выглядеть вот так:
Круто, да? Мы сохранили нормальные теги, но при этом смогли визуально уплотнить информацию.
А теперь о печальном. Сниппет сам по себе нетрудный. Однако статусов и категорий может быть много. К тому же с течением времени наверняка они будут как-то изменяться. Под каждое структурное новшество или изменение придется допиливать css-код. Вручную это делать ужасно скучно.
Эту проблему можно элегантно решить с помощью плагина. Как пилить плагины под Obsidian мне пока что было лень разбираться, поэтому я сделал временное/костыльное решение в виде Python-скрипта. В скрипте прописываются теги статусов и категорий, а также как их нужно отображать. В консоль выводится готовый css-код.
script.py
statuses = { "inbox": "?", "idea": "?", "one-off": "1️⃣", "multistep": "?", "next_action": "?", "reference": "?", "waiting_for": "?", "someday": "?", "regular": "?", } categories = { "knowledge_base": "?️", "health": "?", } print("body {\n --color-of-statuses: rgba(162, 93, 53, 0.1);") print(" --color-of-categories: rgba(0, 255, 255, 0.1);\n}") print( """ /* ░█▀▀░▀█▀░█▀█░▀█▀░█░█░█▀▀ */ /* ░▀▀█░░█░░█▀█░░█░░█░█░▀▀█ */ /* ░▀▀▀░░▀░░▀░▀░░▀░░▀▀▀░▀▀▀ */ """ ) statuses_result = "" for key in statuses: if "_" in key: statuses_result = ( statuses_result + f'.tag[href="#task/{key}"],\n' + f".cm-tag-task{key},\n" + f".cm-tag-task{key} + span.cm-hashtag,\n" + f".cm-tag-task{key} + span.cm-hashtag + .cm-hashtag,\n" ) else: statuses_result = ( statuses_result + f'.tag[href="#task/{key}"],\n' + f".cm-tag-task{key},\n" ) statuses_result = statuses_result[:-2] statuses_result += """ { font-size: 0px; padding: 0; }\n\n""" for key in statuses: if "_" in key: statuses_result = ( statuses_result + f'.tag[href="#task/{key}"]:after,\n' + f".cm-tag-task{key}:after,\n" + f".cm-tag-task{key} + span.cm-hashtag:after,\n" + f".cm-tag-task{key} + span.cm-hashtag + .cm-hashtag:after,\n" ) else: statuses_result = ( statuses_result + f'.tag[href="#task/{key}"]:after,\n' + f".cm-tag-task{key}:after,\n" ) statuses_result = statuses_result[:-2] statuses_result += """ { font-size: var(--font-text-size); }\n\n""" for key in statuses: if "_" in key: statuses_result = ( statuses_result + f'.cm-active .tag[href="#task/{key}"],\n' + f".cm-active .cm-tag-task{key},\n" + f".cm-active .cm-tag-task{key} + span.cm-hashtag,\n" + f".cm-active .cm-tag-task{key} + span.cm-hashtag + .cm-hashtag,\n" ) else: statuses_result = ( statuses_result + f'.cm-active .tag[href="#task/{key}"],\n' + f".cm-active .cm-tag-task{key},\n" ) statuses_result = statuses_result[:-2] statuses_result += """ { font-size: var(--font-text-size); }\n\n""" for key, value in statuses.items(): statuses_result = ( statuses_result + f'.tag[href="#task/{key}"]:after,\n' f".cm-hashtag-begin.cm-tag-task{key}:after " + "{\n" + " border-radius: 3px;\n" + " background-color: var(--color-of-statuses);\n" + f' content: "{value}";\n' + "}\n" ) print(statuses_result) print( """ /* ░█▀▀░█▀█░▀█▀░█▀▀░█▀▀░█▀█░█▀▄░█░█ */ /* ░█░░░█▀█░░█░░█▀▀░█░█░█░█░█▀▄░░█░ */ /* ░▀▀▀░▀░▀░░▀░░▀▀▀░▀▀▀░▀▀▀░▀░▀░░▀░ */ """ ) categories_result = "" for key in categories: if "_" in key: categories_result = ( categories_result + f'.tag[href="#category/{key}"],\n' + f".cm-tag-category{key},\n" + f".cm-tag-category{key} + span.cm-hashtag,\n" + f".cm-tag-category{key} + span.cm-hashtag + .cm-hashtag,\n" ) else: categories_result = ( categories_result + f'.tag[href="#category/{key}"],\n' + f".cm-tag-category{key},\n" ) categories_result = categories_result[:-2] categories_result += """ { font-size: 0px; padding: 0; }\n\n""" for key in categories: if "_" in key: categories_result = ( categories_result + f'.tag[href="#category/{key}"]:after,\n' + f".cm-tag-category{key}:after,\n" + f".cm-tag-category{key} + span.cm-hashtag:after,\n" + f".cm-tag-category{key} + span.cm-hashtag + .cm-hashtag:after,\n" ) else: categories_result = ( categories_result + f'.tag[href="#category/{key}"]:after,\n' + f".cm-tag-category{key}:after,\n" ) categories_result = categories_result[:-2] categories_result += """ { font-size: var(--font-text-size); }\n\n""" for key in categories: if "_" in key: categories_result = ( categories_result + f'.cm-active .tag[href="#category/{key}"],\n' + f".cm-active .cm-tag-category{key},\n" + f".cm-active .cm-tag-category{key} + span.cm-hashtag,\n" + f".cm-active .cm-tag-category{key} + span.cm-hashtag + .cm-hashtag,\n" ) else: categories_result = ( categories_result + f'.cm-active .tag[href="#category/{key}"],\n' + f".cm-active .cm-tag-category{key},\n" ) categories_result = categories_result[:-2] categories_result += """ { font-size: var(--font-text-size); }\n\n""" for key, value in categories.items(): categories_result = ( categories_result + f'.tag[href="#category/{key}"]:after,\n' f".cm-hashtag-begin.cm-tag-category{key}:after " + "{\n" + " border-radius: 3px;\n" + " background-color: var(--color-of-categories);\n" + f' content: "{value}";\n' + "}\n" ) print(categories_result)
Скрипт подразумевает такой запуск
python script.py > tags.css
Python-скрипт – это пока что самый большой костыль в данной системе.
Панель как в Todoist
Все приёмы и идеи, которые я вам показал, можно запихнуть в одну панель с bookmarks.
Вся система на одной панели. При этом сама панель напоминает панель в Todoist. Что как по мне плюс.
Эту же панель можно использовать на телефоне, но об этом чуть-позже.
Чтобы убрать лишние элементы с панели:
bookmarks.css
.workspace-leaf-content[data-type="bookmarks"] .tree-item-icon { display: none; } .workspace-leaf-content[data-type="bookmarks"] .tree-item-self { position: relative; left: -15px; } .view-action.mod-bookmark { display: none; }
Обзор и взаимодействие с системой будет ровно таким же как и тут. Т.е. отличий с обычными таск-менджерами не будет.
Расширение логики и возможностей
Префиксы – приоритет, время, усилие
У нас уже есть префиксы с типом задачи и категорией. В принципе этого должно хватить, чтобы эффективно пользоваться системой. Однако если хочется, ну прям вообще как в таск-менджерах, то стоит добавить приоритет, времязатраты и требуемые усилия.
Приоритет
Мудрить я тут не буду и возьму метод ABCDE.
Символ |
Тег |
Оригинальный смысл |
Альтернативный смысл |
---|---|---|---|
? |
|
Критическая задача, которая должна быть сделана в обязательном порядке |
Важная и срочная задача |
? |
|
Важная, но не критическая задача, которая должна быть сделана |
Важная, но не срочная задача |
? |
|
Полезная/стандартная задача, которую стоит сделать (если её проигнорировать, то не будет значимых последствий) |
Обычная, не срочная задача |
? |
|
Эту задачу стоит делегировать |
Делегировать |
? |
|
Задача с очень низкой полезностью, которую на обзоре стоит пересмотреть или удалить |
Пересмотреть или удалить |
Альтернативный смысл я сделал, чтобы сделать матрицу Эйзенхаура, основываясь на этих же приоритетах.
Зачем нам могут понадобиться приоритеты в персональной системе дел? Я думаю, что основных причин две:
-
Чтобы они мозолили глаза и тем самым вынуждали нас делать сначала важное, а потом уже всё остальное
-
Чтобы был вспомогательный способ рассортировать большой поток задач (основным способом является метка next_action)
Интегрировать приоритеты можно следующим образом.
Мы можем внутри групп, создать ещё одну группировку внутри статусов. Я покажу это на примере категории.
пример группировки на категории
not done tags include category/knowledge_base group by function \ if (task.tags.includes("#task/inbox")) return "%%01%% ? Inbox"; \ if (task.tags.includes("#task/next_action")) return "%%02%% ? Next Action"; \ if (task.tags.includes("#task/one-off")) return "%%03%% 1️⃣ One-off"; \ if (task.tags.includes("#task/multistep")) return "%%04%% ? Multistep"; \ if (task.tags.includes("#task/waiting_for")) return "%%05%% ? Waiting for"; \ if (task.tags.includes("#task/regular")) return "%%06%% ? Regular"; \ if (task.tags.includes("#task/someday")) return "%%07%% ? Someday"; \ if (task.tags.includes("#task/idea")) return "%%08%% ? Idea"; \ if (task.tags.includes("#task/reference")) return "%%09%% ? Reference"; \ return "%%00%% Without status"; group by function \ if (task.tags.includes("#priority/a")) return "%%01%% - ? Important and urgent"; \ if (task.tags.includes("#priority/b")) return "%%02%% - ? Important"; \ if (task.tags.includes("#priority/c")) return "%%03%% - ? Сommon task"; \ if (task.tags.includes("#priority/d")) return "%%04%% - ? Delegate"; \ if (task.tags.includes("#priority/e")) return "%%05%% - ? Eliminate"; \ return "%%99%%"; hide task count hide tags
Я понимаю, что запрос выглядит несколько перегружено. Но, а какие ещё есть варианты? Хотя, как бы с другой стороны, есть люди, которые пытаются в один запрос сразу запихнуть всё. Плохо ли это? Да не особо. Разве что такие запросы порой трудно «расшифровать».
Теперь сделаем матрицу Эйзенхауэра:
-
Верхний левый квадрант (do) –
#priority/a
-
Верхний правый(schedule) —
#priority/b
и#priority/c
-
Нижний левый (delegate) —
#priority/d
и#task/waiting_for
-
Нижний правый (eliminate) —
#priority/e
Матрицу я сделаю на Canvas.
Матрица Эйзенхауэра
Do
not done tags include priority/a hide task count hide tags
Schedule
not done (tags include priority/b) OR (tags include priority/c) group by function \ if (task.tags.includes("#priority/b")) return "%%02%% - ? Important, non-urgent"; \ if (task.tags.includes("#priority/c")) return "%%03%% - ? Сommon task"; \ return "%%99%%"; hide task count hide tags
Delegate
not done (tags include priority/d) OR (tags include task/waiting_for) group by function \ if (task.tags.includes("#priority/d")) return "%%02%% - ? Delegate"; \ if (task.tags.includes("#task/waiting_for")) return "%%03%% - ? Waiting"; \ return "%%99%%"; hide task count hide tags
Eliminate
not done tags include priority/e hide task count hide tags
Время выполнения
Время выполнения нужно в основном, чтобы сбалансировать нагрузку на день.
В Obsidian технически довольно трудно сделать так, чтобы можно было брать задачу и тут же превращать её в таймблок. Точнее трудно это сделать, когда задачи разнесены по разным периодическим заметкам. Так-то плагины для планирования времени в принципе есть.
Я отвлёкся.
Сбалансировать время можно и в системе без наворотов. Нужно лишь просто ввести какие-то ориентиры/правила, к которым стоит стремиться/выполнять.
За базовое утверждение примем мысль, что есть события внешние и внутренние.
Внешние события – это запланированные события или так называемый комитмент. К таким могут относиться, например, митинги и встречи с клиентами.
Внутренние события – это события, которые не начнутся, пока мы их не инициируем и не начнём делать. Например, мы не откроем Anki и не повторим 200 карточек, пока пинаем балду и чатимся в соц. сетях. Или мы не запулим новую фичу в наш личный проект, пока тратим своё свободное время на котиков в Youtube.
Внешние приходят из внешнего реального мира, внутренние исходят из нас самих.
Суть эффективного распределения времени заключатся в том, что когда у нас есть внешние события
нужно заполнить оптимально все свободные промежутки внутренними событиями
Внешние события мы берём из задач, которые имеют дату. Внутренние из всей остальной системы дел.
Для оценки времени будут использоваться следующие теги:
Символ |
Тег |
Смысл |
Пример |
---|---|---|---|
? |
|
Задача, которую можно сделать очень быстро (например, в пределах 15 минут) |
ответить на email |
? |
|
Задача, которая нуждается в небольшой вовлечённости (от 15 минут до нескольких часов) |
подготовить презентацию по готовым результатам |
? |
|
Задача подразумевающая глубокую, сконцентрированную работу (от нескольких часов до 5) |
провести детальное исследование по проекту |
? |
|
Задача, которая требует значительных затрат времени и ресурсов (от 5 часов и более) |
разработка стратегии |
Я указал для всех меток примерное время. Это время является довольно условным – просто, чтобы можно было ориентироваться на что-то конкретное. Однако стоит заметить, что все эти оценки находится в пределах дня. Если задачи подразумевают очень долгое выполнение, то разумнее всё же переделать задачу в (проектную заметку/multistep) или использовать иные способы упорядочивания (например, диаграмму Ганта).
Отображение оценки времени я предлагаю использовать только на задачах с next_action
. Обоснование в том, что эта метка отбирает наиболее актуальные задачи. Будет весьма удобно смотреть на предстоящие события, оценивать свободные части времени и заполнять их именно актуальными задачами.
пример для next_action
not done tags include #task/next_action group by function task.tags.filter( (tag) => tag.includes("#category/") ).map( (tag) => tag.split('/')[1] ? tag.split('/').slice(1, 2) : '') group by function \ if (task.tags.includes("#time/quick")) return "%%02%% - ? fast"; \ if (task.tags.includes("#time/moderate")) return "%%03%% - ? 1 pomodoro"; \ if (task.tags.includes("#time/lengthy")) return "%%03%% - ? long"; \ if (task.tags.includes("#time/long")) return "%%03%% - ? very long"; \ return "%%99%%"; sort by function task.happens.format("YYYY-MM-DD") hide task count hide tags
Сложность или усилие
У оценки сложности похожая обоснованность, что и у оценки времени. За тем исключением, что теперь мы будем распределять не время, а оставшиеся силы.
Ментальную энергию довольно трудно измерить точно. Но зато в моменте ответить на вопрос «Я себя хорошо чувствую? Я бодрый?» можно весьма достоверно. На основе этой возможности и будет работать оценка сложности.
Символ |
Тег |
Смысл |
---|---|---|
❤ |
|
Задача простая – почти не требует ни физических, ни моральных сил |
❤️? |
|
Задача требует заметных затрат ресурсов |
? |
|
Очень трудозатратная или морально сложная задача |
Логика тут довольная простая и она соответствует символам:
-
Easy task. Также просто как нарисовать сердечко | Нет никаких моральных отягощений
-
Если сил мало, то можно выбрать простенькие задачи, которые не требуют как таковой концентрации
-
-
Medium task. Нужно будет своё «сердечко» помучить
-
Если силы уже были потрачены на что-то значимое, но, мы например, отдохнули и в целом чувствуем себя неплохо, то можно выбрать средние задачи
-
Альтернативный смысл. Задача нервная или неприятная. На неё явно придется потратить заметное количество усилий
-
-
Hard task. Сложно на уровне – нарисуй мне достоверно человеческое сердце во всех деталях
-
Если мы полны сил, то можно замахнуться на сложную задачу
-
Альтернативный смысл. Задача, которую нам делать страшно или страшно неприятно. Задача нас может вытрепать или по итогу вообще истощить эго
-
Вообще говоря, есть и другие подходы к оценке сложности задачи, но мне показалось разумным в рамках персональной системы выбрать именно такую.
Оценку сложности можно заюзать во всех статусах, кроме next_action. Идея в том, чтобы добирать из этих статусов задачи, на случай, если в next_action нет задач, которые бы соответствовали нашим текущим запасам сил.
пример для one-off
not done tags include #task/one-off group by function task.tags.filter( (tag) => tag.includes("#category/") ).map( (tag) => tag.split('/')[1] ? tag.split('/').slice(1, 2) : '') group by function \ if (task.tags.includes("#effort/hard")) return "%%01%% - ? hard"; \ if (task.tags.includes("#effort/medium")) return "%%02%% - ❤️? medium"; \ if (task.tags.includes("#effort/easy")) return "%%03%% - ❤ easy"; \ return "%%99%%"; sort by function task.happens.format("YYYY-MM-DD") hide task count hide tags
Сводная таблица группировок
Группы – это первое группирование. Подгруппы – это как нужно сгруппировать внутри групп.
Тип |
Группы |
Подгруппы |
---|---|---|
? inbox |
по упоминанию* |
— |
? next_action |
|
|
1️⃣ one-off |
|
|
? multistep |
|
|
? waiting_for |
|
|
? regular |
|
|
? idea |
|
|
? reference |
|
|
? someday |
|
|
?️ category |
|
|
-
это вот так
group by backlink
Можете как-то поэкспериментировать с этой таблицей. Ну, или подумать о каких-то других способах, которые бы сделали систему ещё удобнее.
Стоит под конец отметить, что приоритет время и сложность – это вещи, которые делают классификацию задач уж слишком многомерной. Кому-то такое нравится и даже помогает в управлении делами. Кому-то наоборот нужен вообще один бинарный классификатор (сейчас/потом). Я нахожусь на стороне баланса – классификаторы должны быть, но не слишком много. Ровно также, например, я считаю, что тайм-блокинг может быть эффективен, но только вперемешку с обычными таск-листами.
Замена URL на markdown-ссылку
Эта функция уже есть в шаблоне для создания задачи, который я прикрепил в самом начале. Просто хотел отдельно сказать, что в нём есть также функция переделывающая URL на markdown-ссылки. Эта операция делается для экономии места.
demo
В качестве альтернативы вы можете конвертировать ссылки с помощью плагина Auto link title или с помощью этого шаблона сразу вставлять markdown-ссылку.
Комментарии
Мне видится два способа как можно делать комментарии к задачам.
-
1-ый вариант – написать комментарий в самом тексте задачи
2-вариант – Явно указать, что у задачи есть комментарий и написать его под задачей
Я вам предлагаю использовать и тот, и тот способ.
Ссылки на других людей в тексте задачи
Без проблем можно добавлять ссылки на людей в задачи. А потом в заметках по этим же людям можно собирать задачи, где они упоминаются.
пример задач и заметки по человеку
Запросы на этот раз будут для dataviewjs.
Упоминания
dv.taskList(dv.pages().file.tasks .where(t => !t.completed) .where(t => !t.text.includes("#task/waiting_for")) .where(t => dv.func.contains(t.outlinks, dv.current().file.link)))
Делегировано
dv.taskList(dv.pages().file.tasks .where(t => !t.completed) .where(t => t.text.includes("#task/waiting_for")) .where(t => dv.func.contains(t.outlinks, dv.current().file.link)))
Смысл этих запросов том, что они ищут задачи, где есть ссылка на текущую заметку.
Чтобы убрать обратные ссылки на периодические заметки в dataviewjs запросах, можно заюзать cssclass remove-dataview-title
remove-dataview-title.css
--- cssclasses: - remove-dataview-title ---
.remove-dataview-title :is(.block-language-dataview, .block-language-dataviewjs) h4 { display: none; }
Чтобы пофиксить проблему с отступами у задач в теме Shimmering focus
tasks.css
/* https://github.com/chrisgrieser/shimmering-focus/issues/304#issuecomment-2107064048 */ :is(.block-language-dataview, .block-language-dataviewjs) ul.contains-task-list { --list-indent: 0.3em !important; padding-inline-start: 0 !important; } .contains-task-list .dataview.task-list-item-checkbox { margin-inline: 0 0.5em !important; }
Поиск задач
Obsidian о нас позаботился, поэтому в нём есть операторы поиска задач. Нам нужно лишь добавить в bookmarks поисковые запросы с этими операторами. Раньше, кстати, приходилось возиться с регулярными выражениями.
Команда для добавления поискового запроса в избранное:
task: task-todo: task-done:
И ещё одна полезная вещь. Чтобы в поиске задачи рендерелись как в заметке, стоит добавить к себе плагин Query Control. Этот плагин ставится через BRAT.
Попытка в недельный календарь
Эта часть максимально экспериментальная.
Идея такая. Помимо того, что можно сделать календарь, который будет показывать задачи на текущей недели, можно ещё отобразить задачи, которые бы явно задавали расписание дня.
Допустим мы создаём периодическую заметку и явно прописываем в ней, чем мы будем заниматься в этот день.
пример периодической заметки
На этот день могут быть запланированы ещё какие-то события. Одно я прописал сразу в расписании, а другое мы увидим на недельном календаре.
weekly tasks
Запросы для расписания привязаны к эмодзи ?
. Это стоит учитывать.
Чтобы показать сегодняшнюю дату (inline-запрос):
`$=dv.span(moment(Date.now()).format('MMMM DD, dddd (YYYY-MM-DD)'))`
Чтобы отобразить недельный календарь
`$=await dv.view("templates/views/week_tasks")`
Как видите, тут всё будет работать через dv.view.
templates/views/week_tasks/view.js
function query(day) { const query = ` due ${day} path includes ${dv.current().file.folder} hide task count hide scheduled date hide due date hide done date hide postpone button `; return (query) } function query_shedule(date) { const query_shedule = ` dv.taskList(dv.pages('"' + dv.current().file.folder + '"') .where(p => p.file.name == "${date}") .file .tasks .where(t => !t.text.includes("#")) ) `; return (query_shedule) } function callout(day, date) { const calloutTitle = day + " - [[periodic/daily/" + date + "|" + date + "]]" + `\n` const content = '#### On this day\n' + '```tasks\n' + query(day) + '\n```' + '\n#### Daily schedule\n' + '```dataviewjs\n' + query_shedule(date) + '\n```' let allText const today = moment().format("YYYY-MM-DD") if (date == today) { allText = `> [!calendar|today]+ ` + calloutTitle + content } else if (date > today) { allText = `> [!calendar]+ ` + calloutTitle + content } else { allText = `> [!calendar]- ` + calloutTitle + content } const lines = allText.split('\n'); return lines.join('\n> ') + '\n' } function calendar() { const weekDayNumber = { Monday: 1, Tuesday: 2, Wednesday: 3, Thursday: 4, Friday: 5, Saturday: 6, Sunday: 7 } for (let day in weekDayNumber) { let date = moment().clone().startOf('isoWeek').add(weekDayNumber[day] - 1, 'days').format("YYYY-MM-DD") dv.paragraph(callout(day, date)) } } calendar()
templates/views/week_tasks/view.css
/* calendar */ .callout[data-callout="calendar"] { --callout-color: 244, 65, 51; --callout-icon: lucide-calendar; } .callout[data-callout="calendar"][data-callout-metadata~="today"], .callout[data-callout="calendar"][data-callout-metadata~="today"] { --callout-color: 0, 128, 0; --callout-icon: lucide-calendar; } .callout[data-callout="calendar"] .callout-content { padding: 0px; margin: 0px; } /* hide dataview title */ :is(.block-language-dataview, .block-language-dataviewjs) h4 { display: none; }
Этот подход максимально экспериментальный. Это можно понять хотя бы из того, что недельный календарь содержит 14 запросов. Это явно неоптимальный способ. Был бы рад, если эту идею/подход кто-нибудь улучшил.
Плагин Datepicker
У плагина Tasks весьма убогий способ задания даты у задачи. Какое-то время, вместо него я юзал вот этот шаблон от anareaty. Однако сейчас появился плагин Datepicker, который решает проблему проставления даты тем способом, который мы и ожидаем – позволяет выбрать её на календаре.
Единственное, что для удобства команду вызова календаря стоит склеить с шаблоном ?
. Это можно сделать с помощью QuickAdd.
Вложенный vault для телефона
У меня в Obsidian установлено многовато плагинов. Из-за них подгрузка на телефоне занимает целую вечность. Загружать всю базу знаний ради того, чтобы две задачки и одну мимолётную идею записать не фонтан.
В качестве решения можно сделать отдельный vault, в котором будет минимальное количество плагинов.
Если вы делали хранилище по моим гайдам, то у вас периодические заметки лежат в папке periodic. Я предлагаю создать в этой папке новый vault.
Для полноты картины можете посмотреть папки, которые есть в моей персональной системе.
Ну, а дальше нужно просто немного всё украсить.
Для начала нужно восстановить панель избранного
Далее стоит немного настроить панель инструментов
Тут самое главное добавить открытие command palette. И ещё обратите внимание, что создание периодической заметки я сделал по свайпу вниз (quick action). Это очень удобно, ибо подразумевает логику: открыл Obsidian, свайпнул вниз, написал нужные заметки.
В command palette для удобства стоит прикрепить шаблон создания задачи
Есть ещё одна полезная вещь.
Вероятно у вас тоже есть функция «замочка» на телефоне
Этот замочек делает так, чтобы Obsidian дольше удерживался в оперативной памяти. Это нужно, чтобы при повторном открытии Obsidian открывался мгновенно.
И на десерт. Открытие Obsidian можно ещё ускорить, если перейти на Olauncher.
Он делает взаимодействие с приложениями максимально минималистичным и быстрым.
В нём есть всего одна главная страница
Открытие Obsidian в таком лаунчере можно установить на свайп
Как видите Obsidian у меня открывается на свайп слева, а браузер на свайп справа.
Плагины, которые у меня установлены в «мобильном» vault:
-
Auto link title. Чтобы переделывать url в markdown-ссылки
-
Calendar. Календарь для навигации по периодическим заметкам
-
Dataview. Чтобы вся система работала
-
Datepicker. Для вставки даты
-
Emoji Shortcodes. Для вставки эмодзи
-
Force note view mode. Чтобы определённые заметки открывались только в режиме чтения
-
Link Favicons. Чтобы рядом с url была икона сайта
-
Note Toolbar. Для быстрой навигации
-
Periodic notes. Для организации периодических заметок
-
Query control. Чтобы в поиске рендерелись результаты
-
Tasks. Чтобы вся система работала
-
Templater. Для создания периодических заметок и чтобы шаблон для создания задач работал
Если так посмотреть на мобильное приложение Obsidian, то оно станет не таким уж и плохим, хах.
Парочка мелких уточнений:
-
Шаблон для создания задач и всё, что будет на панели избранного, нужно положить в такое место, чтобы они были доступны и на телефоне, и на компьютере (т.е. их нужно по итогу положить во вложенный vault)
-
Шаблон создания задачи работает нормально на Android. На IOS я его не проверял
-
Хранилище на телефоне с основным хранилищем у меня синхронизируется с помощью Syncthing
-
На телефоне всё равно писать и обозревать систему не очень удобно, поэтому ревью и всяческие дополнения/расширения/редактирование я делаю через компьютер
-
Т.е. телефон, в большинстве своём, мне нужен, чтобы быстро что-то записать и чтобы посмотреть предстоящие события и актуальные задачи
-
-
CSS для изменения отображения тегов придется продублировать в мобильный vault
-
Вы можете мобильный vault настроить и менять на компьютере
Лёгкое подытоживание и пример
Кажется, что система сложная. Но на деле это не так. Вся система, как мы помним, у нас находится на одной панели.
Чтобы задачи появились в этой системе, задачам нужно давать нужные теги. Все эти теги зашиты в шаблон для создания задачи, т.е. помнить их не нужно. При этом теги меняются через этот же один шаблон.
В начале статьи я показал общую структуру задачи. Теперь можно составить более конкретную схему:
Tasks сам соберёт задачи из периодических заметок, и сам их как надо сгруппирует.
Наша задача просто размышлять и в процессе формировать эти самые задачи.
пример формирования задач
Как видите, я все сделал в видео аутлайна. При этом я сразу добавил ссылки на те или иные техники, или концепции (именно в этом будет проявляться скрещивание системы дел и базы знаний).
Все задачи, которые мы напридумывали, будут агрегироваться в нужных местах. При этом, когда мы будем их делать, то так или иначе будем возвращаться в место, где их записали. И это плюс. Возвращаясь, мы будем видеть все действия, которые приняли, концепции и методы, которые использовали. Это почти как вести проект (проектную заметку), только в более свободной манере.
Ну, и да. Необязательно всё прям так масштабно делать. Можно и просто линейно записывать задачки друг за другом.
Преимущества и недостатки
Преимущества
-
Всё в Obsidian
-
Это плюс и как с точки зрения локального хранения – никто не нагнёт и не выпнет
-
И также плюс с точки зрения возможностей – с текстом, в рамках Obsidian, довольно много можно делать разных операций
-
-
Высокая степень автоматизации
-
Все задачи автоматом агрегируются в соответствии с их типом, категорией, приоритетами и т.д.
-
В том же Todoist автоматизацией можно разве что назвать сложные фильтрующие запросы – во всё остальном мы, либо сильно ограничены, либо нужно всё руками делать
-
-
Высокая скорость создания задач
-
Данную систему можно сделать поверх уже имеющейся
-
Теги можно массово переименовать, если вдруг подобрано какое-то неудачное слово
Недостатки
-
Есть костыли
-
Систему сначала нужно продумать и создать (всё строится на приёмах и хаках, а не по типу «шаблон скачал и всё завелось»)
-
Система расширяемая, но при этом довольно много нужно делать манипуляций с кодом и запросами
-
С календарём всё сложно
-
Есть крайне интересное решение, которое может работать в связке с Tasks. Но оно подразумевает будто бы вообще вся система является системой дел. Ещё данное решение конфликтует с подходом в статье — префиксы делают отображение календаря жутко зашумленным
-
-
Делать массовые редактирования задач удобно, только, когда они находятся рядом. В остальных же случаях всё равно придется много кликать и тапать
-
Obsidian и его плагины работают не сказать бы, что быстро. На 2к активных задач запросы уже прям задумываются. На 5к скорее всего умрут мощный телефон и средний ПК. На 7к и более можно будет пойти потренироваться в зале, приготовить ужин и посмотреть кино, прежде tasks что-то покажет
-
Понятное дело, что запросы ищут по всей системе, во всех файлах. Плюс есть css-код. Да и вообще Obsidian не таск-менеджер. Но всё равно жалко, что такие банальные вещи плохо оптимизированы
-
Тот же Todoist работает всегда одинаково быстро
-
Итог
Рекомендовать такой подход всем, я однозначно не могу. В нём есть костыли и специфические особенности. Однако если вы гик и вам нравится копаться и организовывать подобные системы, то почему бы и нет?
Кстати говоря, сама по себе идея разбить задачу на префикс и контент, на мой взгляд, мощная. Просто реализовать красивым и при этом простым образом в Obsidian её довольно затруднительно.
P.S.
На это раз на статью меня вдохновили не кто-то другой или что-то другое, а наоборот отсутствие оных.
На этом у меня всё.
Задать вопрос или как-то расширить эту статью своим комментарием, вы также можете в telegram-канале. Если статья принесла вам пользу и вы в ответ хотите выразить свою благодарность в материальном виде, то можете сделать это вот тут или с помощью кнопки «Задонатить» (смотрите ниже).
ссылка на оригинал статьи https://habr.com/ru/articles/833654/
Добавить комментарий