Skip to content

Commit

Permalink
feat: add button to schedule/cancel to-do items to today
Browse files Browse the repository at this point in the history
  • Loading branch information
ahonn committed Apr 27, 2022
1 parent c2447cf commit 5909096
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 80 deletions.
23 changes: 8 additions & 15 deletions src/App.tsx
Expand Up @@ -5,13 +5,9 @@ import dayjs from 'dayjs';
import advancedFormat from 'dayjs/plugin/advancedFormat';
import useAppVisible from './hooks/useAppVisible';
import TaskInput, { ITaskInputRef } from './components/TaskInput';
import useUserConfigs, { withUserConfigs } from './hooks/useUserConfigs';
import TaskSection from './components/TaskSection';
import getAnytimeTaskQuery from './querys/anytime';
import getScheduledTaskQuery from './querys/scheduled';
import getTodayTaskQuery from './querys/today';
import { logseq as plugin } from '../package.json';
import { useSWRConfig } from 'swr';
import useAppState, { withAppState } from './hooks/useAppState';

dayjs.extend(advancedFormat);

Expand All @@ -29,17 +25,14 @@ function ErrorFallback({ error }: FallbackProps) {
}

function App() {
const { mutate } = useSWRConfig();
const innerRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<ITaskInputRef>(null);
const visible = useAppVisible();
const userConfigs = useUserConfigs();
const { userConfigs, refresh, tasks } = useAppState();

useEffect(() => {
if (visible) {
mutate(getTodayTaskQuery());
mutate(getScheduledTaskQuery());
mutate(getAnytimeTaskQuery());
refresh();
inputRef.current?.focus();
}
}, [visible]);
Expand All @@ -65,7 +58,7 @@ function App() {
`${preferredTodo} ${content}`,
{ isPageBlock: true, before: false },
);
mutate(getTodayTaskQuery());
refresh();
};

return (
Expand All @@ -78,9 +71,9 @@ function App() {
<ErrorBoundary FallbackComponent={ErrorFallback}>
<TaskInput ref={inputRef} onCreateTask={createNewTask} />
<div>
<TaskSection title="Today" query={getTodayTaskQuery()} />
<TaskSection title="Scheduled" query={getScheduledTaskQuery()} />
<TaskSection title="Anytime" query={getAnytimeTaskQuery()} />
<TaskSection title="Today" tasks={tasks.today} />
<TaskSection title="Scheduled" tasks={tasks.scheduled} />
<TaskSection title="Anytime" tasks={tasks.anytime} />
</div>
</ErrorBoundary>
</div>
Expand All @@ -89,4 +82,4 @@ function App() {
);
}

export default withUserConfigs(App);
export default withAppState(App);
48 changes: 28 additions & 20 deletions src/components/TaskItem.tsx
Expand Up @@ -2,49 +2,47 @@ import React, { useMemo } from 'react';
import classnames from 'classnames';
import dayjs from 'dayjs';
import Checkbox from 'rc-checkbox';
import { InfoCircle } from 'tabler-icons-react';
import { ArrowDownCircle, BrightnessUp } from 'tabler-icons-react';
import useUserConfigs from '../hooks/useUserConfigs';
import useTask from '../hooks/useTask';
import 'rc-checkbox/assets/index.css';
import { TaskEntityObject } from '../models/TaskEntity';
import 'rc-checkbox/assets/index.css';

export interface ITaskItemProps {
item: TaskEntityObject;
onChange(task: TaskEntityObject): void;
}

const TaskItem: React.FC<ITaskItemProps> = (props) => {
const { item: task, onChange } = props;
const { item } = props;
const { preferredDateFormat } = useUserConfigs();
const { uuid, completed, scheduled, content, toggle } = useTask(task);
const [checked, setChecked] = React.useState(completed);
const task = useTask(item);
const [checked, setChecked] = React.useState(task.completed);

const isExpiredTask = useMemo(() => {
if (!scheduled) {
if (!task.scheduled) {
return false;
}
const date = dayjs(scheduled.toString(), 'YYYYMMDD');
const date = dayjs(task.scheduled.toString(), 'YYYYMMDD');
return date.isBefore(dayjs(), 'day');
}, [scheduled]);
}, [task.scheduled]);

const handleTaskChange = async () => {
await toggle();
await task.toggle();
setChecked(!checked);
onChange(task);
};

const openTaskBlock = () => {
window.logseq.Editor.openInRightSidebar(uuid);
task.openTask();
window.logseq.hideMainUI();
};

const contentClassName = classnames('line-clamp-3', {
const contentClassName = classnames('line-clamp-3 cursor-pointer', {
'line-through': checked,
'text-gray-400': checked,
});

return (
<div key={uuid} className="flex flex-row pb-1" data-uuid={uuid}>
<div key={task.uuid} className="flex flex-row pb-1">
<div>
<Checkbox
checked={checked}
Expand All @@ -55,21 +53,31 @@ const TaskItem: React.FC<ITaskItemProps> = (props) => {
<div className="flex-1 border-b border-gray-100 pb-2 pt-1 text-sm leading-normal break-all">
<div className="flex justify-between items-center">
<div className="flex-col">
<p className={contentClassName}>{content}</p>
{scheduled && (
<p className={contentClassName} onClick={openTaskBlock}>
{task.content}
</p>
{task.scheduled && (
<time
className={classnames('text-xs', {
'text-gray-400': !isExpiredTask,
'text-red-400': isExpiredTask,
})}
>
{dayjs(scheduled.toString(), 'YYYYMMDD').format(preferredDateFormat)}
{dayjs(task.scheduled.toString(), 'YYYYMMDD').format(
preferredDateFormat,
)}
</time>
)}
</div>
<div className="pl-2 pr-1" onClick={openTaskBlock}>
<InfoCircle size={20} className="stroke-gray-300 cursor-pointer" />
</div>
{task.isTodayScheduled ? (
<div className="pl-2 pr-1" onClick={() => task.setScheduled(null)}>
<ArrowDownCircle size={22} className="stroke-gray-300 cursor-pointer" />
</div>
) : (
<div className="pl-2 pr-1" onClick={() => task.setScheduled(new Date())}>
<BrightnessUp size={22} className="stroke-gray-300 cursor-pointer" />
</div>
)}
</div>
</div>
</div>
Expand Down
14 changes: 4 additions & 10 deletions src/components/TaskSection.tsx
@@ -1,33 +1,27 @@
import React from 'react';
import useTaskQuery from '../hooks/useTaskQuery';
import { TaskEntityObject } from '../models/TaskEntity';
import TaskItem from './TaskItem';

export interface ITaskSectionProps {
title: string;
query: string;
tasks: TaskEntityObject[];
}

const TaskSection: React.FC<ITaskSectionProps> = (props) => {
const { title, query } = props;
const { data: tasks, mutate } = useTaskQuery(query);
const { title, tasks } = props;

if (tasks.length === 0) {
return null;
}

const handleTaskChange = () => {
mutate();
};

return (
<div className="py-2">
<div className="py-1">
<h2 className="py-1 text-red-500">{title}</h2>
<div>
{tasks.map((task) => (
<TaskItem
key={task.uuid}
item={task}
onChange={handleTaskChange}
/>
))}
</div>
Expand Down
69 changes: 69 additions & 0 deletions src/hooks/useAppState.tsx
@@ -0,0 +1,69 @@
import { AppUserConfigs } from '@logseq/libs/dist/LSPlugin.user';
import React, { useCallback, useContext, useMemo } from 'react';
import { TaskEntityObject } from '../models/TaskEntity';
import getAnytimeTaskQuery from '../querys/anytime';
import getScheduledTaskQuery from '../querys/scheduled';
import getTodayTaskQuery from '../querys/today';
import useTaskQuery from './useTaskQuery';
import useUserConfigs, { DEFAULT_USER_CONFIGS } from './useUserConfigs';

export interface IAppState {
userConfigs: AppUserConfigs,
tasks: {
today: TaskEntityObject[],
scheduled: TaskEntityObject[],
anytime: TaskEntityObject[],
},
refresh(): void,
}

const AppStateContext = React.createContext<IAppState>({
userConfigs: DEFAULT_USER_CONFIGS,
tasks: {
today: [],
scheduled: [],
anytime: [],
},
refresh: () => {},
});

const useAppState = () => {
const appState = useContext(AppStateContext);
return appState;
};

export const withAppState = <P extends {}>(
WrapComponent: React.ComponentType<P>,
) => {
const WithAppState: typeof WrapComponent = (props) => {
const userConfigs = useUserConfigs();
const todayTask = useTaskQuery(getTodayTaskQuery());
const scheduledTask = useTaskQuery(getScheduledTaskQuery());
const anytimeTask = useTaskQuery(getAnytimeTaskQuery());

const refresh = useCallback(() => {
todayTask.mutate();
scheduledTask.mutate();
anytimeTask.mutate();
}, [todayTask, scheduledTask, anytimeTask]);

const state = useMemo(() => ({
userConfigs,
tasks: {
today: todayTask.data || [],
scheduled: scheduledTask.data || [],
anytime: anytimeTask.data || [],
},
refresh,
}), [userConfigs, todayTask, scheduledTask, anytimeTask, refresh]);

return (
<AppStateContext.Provider value={state}>
<WrapComponent {...props} />
</AppStateContext.Provider>
);
};
return WithAppState;
};

export default useAppState;
49 changes: 45 additions & 4 deletions src/hooks/useTask.ts
@@ -1,10 +1,17 @@
import dayjs from 'dayjs';
import { useCallback, useMemo } from 'react';
import { TaskEntityObject, TaskMarker } from '../models/TaskEntity';
import useUserConfigs from './useUserConfigs';
import useAppState from './useAppState';

const useTask = (task: TaskEntityObject) => {
const { uuid, marker, scheduled, completed } = task;
const { preferredTodo } = useUserConfigs();
const { uuid, marker, scheduled, completed, page } = task;
const { userConfigs, refresh } = useAppState();
const { preferredTodo } = userConfigs;

const isTodayScheduled = useMemo(() => {
if (!scheduled) return false;
return dayjs(new Date()).format('YYYYMMDD') === scheduled.toString();
}, [scheduled]);

const content = useMemo(() => {
let content = task.content;
Expand All @@ -21,15 +28,49 @@ const useTask = (task: TaskEntityObject) => {
uuid,
task.content.replace(marker, nextMarker),
);
}, [completed, preferredTodo, task]);
refresh();
}, [completed, preferredTodo, task, refresh]);

const openTask = useCallback(async () => {
window.logseq.Editor.scrollToBlockInPage(task.page.uuid, uuid);
}, [uuid]);

const setScheduled = useCallback(async (date: Date | null) => {
let nextContent = task.content;
if (date === null) {
nextContent = task.content.replace(/SCHEDULED: <[^>]+>/, '');
await window.logseq.Editor.updateBlock(uuid, nextContent);
refresh();
return;
}

const scheduledString = `SCHEDULED: <${dayjs(date).format('YYYY-MM-DD ddd')}>`;
if (task.content.includes('SCHEDULED')) {
nextContent = task.content.replace(
/SCHEDULED: <[^>]+>/,
scheduledString,
);
} else {
const lines = task.content.split('\n');
lines.splice(1, 0, scheduledString);
nextContent = lines.join('\n');
}

await window.logseq.Editor.updateBlock(uuid, nextContent);
refresh();
}, [task, refresh]);

return {
uuid,
marker,
content,
scheduled,
completed,
page,
isTodayScheduled,
toggle,
openTask,
setScheduled,
};
};

Expand Down

0 comments on commit 5909096

Please sign in to comment.