diff --git a/CHANGELOG.md b/CHANGELOG.md index a2a9ac0..fae0da7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,20 +4,23 @@ - Add support for [Full Calendar full notes](https://github.com/joshuatazrein/obsidian-time-ruler/issues/10#issuecomment-1655804209) ## Planned -- Option to [hide/show headings](https://github.com/joshuatazrein/obsidian-time-ruler/issues/11#issuecomment-1655862428) to reduce visual clutter - Add support for [task repeats](https://github.com/joshuatazrein/obsidian-time-ruler/issues/5#issuecomment-1646958839) - Option to [add tasks at start or end of headings](https://github.com/joshuatazrein/obsidian-time-ruler/issues/12) - Right-click option to [schedule tasks for now](https://github.com/joshuatazrein/obsidian-time-ruler/issues/16#event-9959008621) - More specific Dataview custom filter [at task level](https://github.com/joshuatazrein/obsidian-time-ruler/issues/18) - Options to drag [deadlines and reminder times](https://github.com/joshuatazrein/obsidian-time-ruler/issues/20) in addition to scheduled time -- A [simple mode](https://github.com/joshuatazrein/obsidian-time-ruler/issues/21) with `HH:mm-HH:mm` formatting for scheduled times. ## Considering - Providing [names for time blocks](https://github.com/joshuatazrein/obsidian-time-ruler/issues/11#issuecomment-1655862428) (perhaps by moving tasks with durations to the "name" field of a time block) +- Option to [hide/show headings](https://github.com/joshuatazrein/obsidian-time-ruler/issues/11#issuecomment-1655862428) to reduce visual clutter # Changelog -## 1.0.6 (Upcoming) +## 1.1.0 (Upcoming) +- **Added:** Support [emoji and custom status](https://github.com/joshuatazrein/obsidian-time-ruler/issues/26) displaying in tasks +- **Added:** Filter by [custom status](https://github.com/joshuatazrein/obsidian-time-ruler/issues/25) +- **Added:** A [simple mode](https://github.com/joshuatazrein/obsidian-time-ruler/issues/21) with `HH:mm-HH:mm` formatting for scheduled times. +- **Added:** larger drop target for [dates](https://github.com/joshuatazrein/obsidian-time-ruler/issues/24) - **Improved:** Moved search, refresh, and view buttons to a collapsible menu ## 1.0.5 (8/6/2023) diff --git a/src/components/Droppable.tsx b/src/components/Droppable.tsx index ca48027..fd31ef9 100644 --- a/src/components/Droppable.tsx +++ b/src/components/Droppable.tsx @@ -5,7 +5,7 @@ export default function Droppable({ children, id, data, - ref + ref, }: { children: JSX.Element id: string @@ -14,16 +14,18 @@ export default function Droppable({ }) { const { isOver, setNodeRef } = useDroppable({ id, - data + data, }) return cloneElement(children, { ref: ref - ? node => { + ? (node) => { ref(node) setNodeRef(node) } : setNodeRef, - className: `${children.props.className} ${isOver ? '!bg-selection' : ''}` + className: `${children.props.className} rounded-lg ${ + isOver ? '!bg-selection' : '' + }`, }) } diff --git a/src/components/Search.tsx b/src/components/Search.tsx index 57742b3..c205a5b 100644 --- a/src/components/Search.tsx +++ b/src/components/Search.tsx @@ -132,6 +132,18 @@ export default function Search() { return searchTasks }, [tasksByHeading]) + const [status, setStatus] = useState(null) + + const allStatuses = useMemo(() => { + return [ + ...new Set( + Object.values(tasksByHeading) + .flat() + .map((task) => task.status) + ), + ].sort() + }, [tasksByHeading]) + const searching = !!searchStatus return ( @@ -177,6 +189,30 @@ export default function Search() { placeholder='path: heading: title: tag: priority:' ref={inputFrame} > + {allStatuses.length > 1 && ( +
+ +
+ + {allStatuses.map((status) => ( + + ))} +
+
+ )} {typeof searchStatus === 'string' && (
@@ -216,6 +252,7 @@ export default function Search() { const filteredTasks = isShowingTasks ? tasksByHeading[heading]?.filter( (task) => + !(status && task.status !== status) && searchExp.test(searchTasks[task.id]) && testViewMode(task) ) ?? [] @@ -226,7 +263,7 @@ export default function Search() { ) return ( - {(searchStatus === 'all' || + {((searchStatus === 'all' && !status) || !isShowingTasks || filteredTasks.length > 0) && (
diff --git a/src/components/Task.tsx b/src/components/Task.tsx index 43e0a9c..7cd7096 100644 --- a/src/components/Task.tsx +++ b/src/components/Task.tsx @@ -104,6 +104,8 @@ export default function Task({ ).replace(/\.md/, '') }, [type, task.path]) + if (task.title.includes('pioneer')) console.log(task.title, task) + if (!task) return <> return ( @@ -131,10 +133,12 @@ export default function Task({
false} onClick={() => openTask(task)} > diff --git a/src/components/Timeline.tsx b/src/components/Timeline.tsx index 1a3ac39..f9df88f 100644 --- a/src/components/Timeline.tsx +++ b/src/components/Timeline.tsx @@ -37,19 +37,6 @@ export default function Timeline({ ) }, shallow) - // const filterAllDayChildren = (tasks: TaskProps[]) => { - // const thisTaskIds = new Set(tasks.map((task) => task.id)) - // return tasks.filter( - // (task) => - // !( - // task.parent && - // thisTaskIds.has(task.parent) && - // task.scheduled && - // isDateISO(task.scheduled) - // ) - // ) - // } - const isToday = startISO.slice(0, 10) === (DateTime.now().toISODate() as string) @@ -59,12 +46,14 @@ export default function Timeline({ const allDayTasks: TaskProps[] = [] _.forEach(state.tasks, (task) => { const scheduledForToday = + !task.completion && task.scheduled && task.scheduled < endISO && (isToday || task.scheduled >= startISO) if ( !scheduledForToday && task.due && + !task.completion && (task.due >= startISO || (isToday && task.due < endISO)) ) { dueTasks.push(task) @@ -157,8 +146,8 @@ export default function Timeline({
@@ -201,12 +190,18 @@ export default function Timeline({ )}
{timeSpan} + +
+
diff --git a/src/main.ts b/src/main.ts index 56b567f..fc15508 100644 --- a/src/main.ts +++ b/src/main.ts @@ -26,7 +26,7 @@ export const DEFAULT_SETTINGS: TimeRulerSettings = { fileOrder: [], customStatus: { include: false, - statuses: '-', + statuses: 'x-', }, } diff --git a/src/services/obsidianApi.ts b/src/services/obsidianApi.ts index d0fcdeb..4743e2c 100644 --- a/src/services/obsidianApi.ts +++ b/src/services/obsidianApi.ts @@ -94,8 +94,6 @@ export default class ObsidianAPI extends Component { .filter((task) => { return ( taskTest.test(task.status) === this.settings.customStatus.include && - !task.completion && - !task.completed && !(this.excludePaths && this.excludePaths.test(task.path)) && !( task.start && diff --git a/src/services/parser.ts b/src/services/parser.ts index f38c32a..f42016e 100644 --- a/src/services/parser.ts +++ b/src/services/parser.ts @@ -7,6 +7,8 @@ import { keyToTasksEmoji, priorityKeyToNumber, priorityNumberToKey, + priorityNumberToSimplePriority, + simplePriorityToNumber, } from '../types/enums' import _ from 'lodash' import { isDateISO } from './util' @@ -29,13 +31,20 @@ export function textToTask(item: any): TaskProps { const REMINDER_MATCH = new RegExp( ` ?${keyToTasksEmoji.reminder} ?(${ISO_MATCH}( \\d{2}:\\d{2})?)|\\(@(\\d{4}-\\d{2}-\\d{2}( \\d{2}:\\d{2})?)\\)|@\\{(\\d{4}-\\d{2}-\\d{2}( \\d{2}:\\d{2})?)\\}` ) + const SIMPLE_SCHEDULED = /^\d+:\d+( ?- ?\d+:\d+)?/ + const SIMPLE_PRIORITY = / (?|!|!!|!!!)$/ + const SIMPLE_DUE = / > (\d{4}-d{2}-d{2})/ - const originalTitle: string = (item.text.match(/(.*?)(\n|$)/)?.[1] ?? '') + const titleLine: string = item.text.match(/(.*?)(\n|$)/)?.[1] ?? '' + const originalTitle: string = titleLine .replace(INLINE_FIELD_SEARCH, '') .replace(TASKS_REPEAT_SEARCH, '') .replace(TASKS_EMOJI_SEARCH, '') .replace(TAG_SEARCH, '') .replace(REMINDER_MATCH, '') + .replace(SIMPLE_SCHEDULED, '') + .replace(SIMPLE_DUE, '') + .replace(SIMPLE_PRIORITY, '') let title: string = originalTitle .replace(MD_LINK_SEARCH, '$1') .replace(LINK_SEARCH, '$1') @@ -89,6 +98,7 @@ export function textToTask(item: any): TaskProps { const parseScheduled = () => { let scheduled = item.scheduled as DateTime | undefined let isDate: boolean = false + if (!scheduled) { let date = item.date as string | undefined const testDailyNoteTask = !date && item.parent === undefined @@ -184,17 +194,48 @@ export function textToTask(item: any): TaskProps { } const parseRepeat = () => { - return item['repeat'] ?? item.text.match(TASKS_REPEAT_SEARCH)?.[1] + return item['repeat'] ?? titleLine.match(TASKS_REPEAT_SEARCH)?.[1] } - const scheduled = parseScheduled() - const due = parseDateKey('due') + const parseSimple = () => { + let simpleScheduled: string | undefined, + simpleLength: TaskProps['length'] | undefined, + simpleDue: TaskProps['due'] | undefined, + simplePriority: TaskProps['priority'] | undefined + const scheduled = titleLine.match(SIMPLE_SCHEDULED)?.[0] + if (scheduled) { + const [startTime, endTime] = scheduled.split(/ ?- ?/) + const date = item.section.path + .replace(/\.md$/, '') + .match(new RegExp(`${ISO_MATCH}$`))?.[0] + if (date && startTime) { + simpleScheduled = date + 'T' + startTime + } + if (simpleScheduled && endTime) { + const duration = DateTime.fromISO(date + 'T' + endTime) + .diff(DateTime.fromISO(simpleScheduled)) + .shiftTo('hour', 'minute') + simpleLength = { hour: duration.hours, minute: duration.minutes } + } + } + const priorityMatch = titleLine.match(SIMPLE_PRIORITY)?.[1] + simplePriority = priorityMatch + ? simplePriorityToNumber[priorityMatch] + : undefined + simpleDue = titleLine.match(SIMPLE_DUE)?.[1] + return { simpleScheduled, simpleLength, simplePriority, simpleDue } + } + + const { simpleScheduled, simpleLength, simpleDue, simplePriority } = + parseSimple() + const scheduled = simpleScheduled ?? parseScheduled() + const due = simpleDue ?? parseDateKey('due') const completion = parseDateKey('completion') const start = parseDateKey('start') const created = parseDateKey('created') const repeat = parseRepeat() - const priority = parsePriority() - const length = parseLength(scheduled) + const priority = simplePriority ?? parsePriority() + const length = simpleLength ?? parseLength(scheduled) const reminder = parseReminder() return { @@ -231,6 +272,7 @@ const detectFieldFormat = ( defaultFormat: FieldFormat['main'] ): FieldFormat => { const parseMain = (): FieldFormat['main'] => { + if (/^\d+:\d+( ?- ?\d+:\d+)? /.test(text)) return 'simple' for (let emoji of Object.keys(TasksEmojiToKey)) { if (text.contains(emoji)) return 'tasks' } @@ -283,6 +325,34 @@ export function taskToText( } switch (main) { + case 'simple': + if (task.scheduled && !isDateISO(task.scheduled)) { + let scheduledTime = task.scheduled.slice(11, 16) + if (task.length) { + const end = DateTime.fromISO(task.scheduled).plus(task.length) + scheduledTime += ` - ${end.toFormat('HH:mm')}` + } + draft = + draft.slice(0, 6) + + scheduledTime + + ' ' + + draft.slice(6).replace(/^\s+/, '') + } + if (task.due) draft += ` > ${task.due}` + if (task.priority && task.priority !== TaskPriorities.DEFAULT) { + draft += ` ${priorityNumberToSimplePriority[task.priority]}` + } + if (task.repeat) draft += ` [repeat:: ${task.repeat}]` + if (task.start) { + draft += ` [start:: ${task.start}]` + } + if (task.created) { + draft += ` [created:: ${task.created}]` + } + if (task.completion) { + draft += ` [completion:: ${task.completion}]` + } + break case 'dataview': if (task.scheduled) draft += ` [scheduled:: ${task.scheduled}]` draft += formatReminder() diff --git a/src/types/enums.ts b/src/types/enums.ts index da99059..dd244cc 100644 --- a/src/types/enums.ts +++ b/src/types/enums.ts @@ -22,6 +22,14 @@ export const priorityKeyToNumber = { default: TaskPriorities.DEFAULT, } +export const simplePriorityToNumber = { + '?': TaskPriorities.LOWEST, + '!': TaskPriorities.LOW, + '!!': TaskPriorities.HIGH, + '!!!': TaskPriorities.HIGHEST, +} + +export const priorityNumberToSimplePriority = _.invert(simplePriorityToNumber) export const priorityNumberToKey = _.invert(priorityKeyToNumber) export const keyToTasksEmoji = { diff --git a/src/types/index.d.ts b/src/types/index.d.ts index be70848..082f6d9 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -5,7 +5,7 @@ import { TaskComponentProps } from '../components/Task' declare global { type FieldFormat = { - main: 'dataview' | 'full-calendar' | 'tasks' + main: 'dataview' | 'full-calendar' | 'tasks' | 'simple' reminder: 'native' | 'tasks' | 'kanban' }