@tuturuuu/ui 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/package.json +4 -4
- package/src/components/ui/calendar-app/components/habits-panel.tsx +5 -2
- package/src/components/ui/calendar-app/components/priority-view.tsx +15 -7
- package/src/components/ui/calendar-app/components/scheduling-dialog.tsx +7 -3
- package/src/components/ui/calendar-app/components/task-scheduler-panel.tsx +7 -3
- package/src/components/ui/calendar-app/components/time-tracker/index.tsx +14 -9
- package/src/components/ui/custom/settings/task-settings.tsx +61 -2
- package/src/components/ui/custom/tables/data-table.tsx +58 -8
- package/src/components/ui/tu-do/boards/boardId/board-column.tsx +4 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/auto-scroll.test.ts +118 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/auto-scroll.ts +144 -70
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.test.ts +63 -1
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +21 -10
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +6 -0
- package/src/components/ui/tu-do/boards/boardId/kanban.tsx +4 -2
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.test.ts +11 -1
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.ts +7 -3
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-resource-context.test.ts +113 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-resource-context.ts +50 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +65 -18
- package/src/components/ui/tu-do/boards/boardId/timeline-board.tsx +1 -1
- package/src/components/ui/tu-do/cycles/task-cycles-client.tsx +23 -14
- package/src/components/ui/tu-do/estimates/use-task-estimates.ts +9 -4
- package/src/components/ui/tu-do/habits/client.tsx +4 -1
- package/src/components/ui/tu-do/my-tasks/__tests__/use-task-context-actions.test.ts +6 -3
- package/src/components/ui/tu-do/my-tasks/task-list-with-completion.tsx +19 -11
- package/src/components/ui/tu-do/my-tasks/use-my-tasks-query.ts +15 -6
- package/src/components/ui/tu-do/my-tasks/use-my-tasks-state.ts +18 -13
- package/src/components/ui/tu-do/my-tasks/use-task-context-actions.ts +17 -10
- package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +70 -0
- package/src/components/ui/tu-do/shared/board-views.tsx +33 -3
- package/src/components/ui/tu-do/shared/fade-setting-initializer.tsx +3 -1
- package/src/components/ui/tu-do/shared/list-view.tsx +1 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/quick-settings-popover.tsx +6 -2
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/__tests__/use-update-shared-task.test.ts +9 -5
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/task-api.ts +13 -8
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-overrides.ts +21 -9
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-update-shared-task.ts +10 -5
- package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +4 -2
- package/src/components/ui/tu-do/shared/task-estimation-picker.tsx +5 -2
- package/src/components/ui/tu-do/shared/task-quick-create-target-list.ts +20 -0
- package/src/components/ui/tu-do/shared/task-row-actions-menu.tsx +1 -1
- package/src/hooks/__tests__/use-task-actions.test.tsx +51 -0
- package/src/hooks/task-actions-personal-external.ts +2 -3
- package/src/hooks/use-board-actions.ts +27 -17
- package/src/hooks/use-calendar.tsx +7 -3
- package/src/hooks/use-settings-dialog-shortcut.ts +51 -0
- package/src/hooks/use-user-config.ts +10 -2
- package/src/lib/task-personal-external.ts +49 -0
- package/src/lib/tasks-app-url.ts +85 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect } from 'react';
|
|
4
|
+
|
|
5
|
+
interface UseSettingsDialogShortcutOptions {
|
|
6
|
+
enabled: boolean;
|
|
7
|
+
onOpen: () => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function isEditableShortcutTarget(target: EventTarget | null) {
|
|
11
|
+
if (!(target instanceof HTMLElement)) return false;
|
|
12
|
+
|
|
13
|
+
const tagName = target.tagName.toLowerCase();
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
target.isContentEditable ||
|
|
17
|
+
tagName === 'input' ||
|
|
18
|
+
tagName === 'textarea' ||
|
|
19
|
+
tagName === 'select'
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isSettingsDialogShortcut(event: KeyboardEvent) {
|
|
24
|
+
return (event.metaKey || event.ctrlKey) && !event.altKey && event.key === ',';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Opens the app settings dialog on Cmd/Ctrl+, — the platform-wide convention.
|
|
29
|
+
* Ignores the shortcut while typing in editable fields and when another handler
|
|
30
|
+
* has already called preventDefault(). Shared by apps/web and every satellite
|
|
31
|
+
* app (wired once in the satellite user-nav shell).
|
|
32
|
+
*/
|
|
33
|
+
export function useSettingsDialogShortcut({
|
|
34
|
+
enabled,
|
|
35
|
+
onOpen,
|
|
36
|
+
}: UseSettingsDialogShortcutOptions) {
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (!enabled) return;
|
|
39
|
+
|
|
40
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
41
|
+
if (event.defaultPrevented || !isSettingsDialogShortcut(event)) return;
|
|
42
|
+
if (isEditableShortcutTarget(event.target)) return;
|
|
43
|
+
|
|
44
|
+
event.preventDefault();
|
|
45
|
+
onOpen();
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
49
|
+
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
50
|
+
}, [enabled, onOpen]);
|
|
51
|
+
}
|
|
@@ -11,14 +11,22 @@ import { getUserConfig, updateUserConfig } from '@tuturuuu/internal-api/users';
|
|
|
11
11
|
* @param defaultValue - Default value if config doesn't exist
|
|
12
12
|
* @returns Query result with the config value
|
|
13
13
|
*/
|
|
14
|
-
export function useUserConfig(
|
|
14
|
+
export function useUserConfig(
|
|
15
|
+
configId: string,
|
|
16
|
+
defaultValue: string = '',
|
|
17
|
+
options?: {
|
|
18
|
+
enabled?: boolean;
|
|
19
|
+
staleTime?: number;
|
|
20
|
+
}
|
|
21
|
+
) {
|
|
15
22
|
return useQuery({
|
|
16
23
|
queryKey: ['user-config', configId],
|
|
17
24
|
queryFn: async () => {
|
|
18
25
|
const data = await getUserConfig(configId);
|
|
19
26
|
return (data.value as string) ?? defaultValue;
|
|
20
27
|
},
|
|
21
|
-
|
|
28
|
+
enabled: options?.enabled !== false && Boolean(configId),
|
|
29
|
+
staleTime: options?.staleTime ?? 5 * 60 * 1000, // 5 minutes
|
|
22
30
|
});
|
|
23
31
|
}
|
|
24
32
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Task } from '@tuturuuu/types/primitives/Task';
|
|
2
|
+
import { isPersonalExternalStagingListId } from '@tuturuuu/utils/task-helper';
|
|
3
|
+
|
|
4
|
+
function hasValue(value: string | null | undefined): value is string {
|
|
5
|
+
return typeof value === 'string' && value.length > 0;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function isPersonalExternalOverlayTask(task?: Task | null) {
|
|
9
|
+
if (!task) return false;
|
|
10
|
+
|
|
11
|
+
if (task.is_personal_external === true) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (task.is_personal_external === false) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (isPersonalExternalStagingListId(task.list_id)) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const personalBoardId = hasValue(task.personal_board_id)
|
|
24
|
+
? task.personal_board_id
|
|
25
|
+
: null;
|
|
26
|
+
const personalListId = hasValue(task.personal_list_id)
|
|
27
|
+
? task.personal_list_id
|
|
28
|
+
: null;
|
|
29
|
+
|
|
30
|
+
if (!personalBoardId && !personalListId) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (
|
|
35
|
+
hasValue(task.source_board_id) &&
|
|
36
|
+
personalBoardId &&
|
|
37
|
+
task.source_board_id !== personalBoardId
|
|
38
|
+
) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
!hasValue(task.source_board_id) &&
|
|
44
|
+
hasValue(task.source_workspace_id) &&
|
|
45
|
+
hasValue(task.source_list_id) &&
|
|
46
|
+
personalListId !== null &&
|
|
47
|
+
task.source_list_id !== personalListId
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
function normalizeOrigin(value?: string) {
|
|
2
|
+
if (!value) {
|
|
3
|
+
return null;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const [firstValue] = value
|
|
7
|
+
.split(/[,\n]/u)
|
|
8
|
+
.map((entry) => entry.trim())
|
|
9
|
+
.filter(Boolean);
|
|
10
|
+
|
|
11
|
+
if (!firstValue) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const normalized = /^[a-z]+:\/\//iu.test(firstValue)
|
|
16
|
+
? firstValue
|
|
17
|
+
: `https://${firstValue}`;
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
return new URL(normalized).origin;
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getTasksAppOriginForBrowser() {
|
|
27
|
+
const configured =
|
|
28
|
+
normalizeOrigin(process.env.NEXT_PUBLIC_TASKS_APP_URL) ??
|
|
29
|
+
normalizeOrigin(process.env.NEXT_PUBLIC_TUDO_APP_URL);
|
|
30
|
+
|
|
31
|
+
if (configured) {
|
|
32
|
+
return configured;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (typeof window === 'undefined') {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const { hostname, port, protocol } = window.location;
|
|
40
|
+
const normalizedHostname = hostname.toLowerCase();
|
|
41
|
+
|
|
42
|
+
if (
|
|
43
|
+
normalizedHostname === 'tasks.tuturuuu.com' ||
|
|
44
|
+
normalizedHostname === 'tasks.tuturuuu.localhost' ||
|
|
45
|
+
port === '7809'
|
|
46
|
+
) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (
|
|
51
|
+
normalizedHostname === 'localhost' ||
|
|
52
|
+
normalizedHostname === '127.0.0.1' ||
|
|
53
|
+
normalizedHostname === '::1' ||
|
|
54
|
+
normalizedHostname === '[::1]'
|
|
55
|
+
) {
|
|
56
|
+
return port ? `${protocol}//${hostname}:7809` : null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (
|
|
60
|
+
normalizedHostname === 'tuturuuu.localhost' ||
|
|
61
|
+
normalizedHostname.endsWith('.tuturuuu.localhost')
|
|
62
|
+
) {
|
|
63
|
+
return `${protocol}//tasks.tuturuuu.localhost`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (
|
|
67
|
+
normalizedHostname === 'tuturuuu.com' ||
|
|
68
|
+
normalizedHostname.endsWith('.tuturuuu.com')
|
|
69
|
+
) {
|
|
70
|
+
return 'https://tasks.tuturuuu.com';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function getTasksAppUrl(path: string) {
|
|
77
|
+
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
|
78
|
+
const origin = getTasksAppOriginForBrowser();
|
|
79
|
+
|
|
80
|
+
return origin ? new URL(normalizedPath, origin).toString() : normalizedPath;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function getTaskApiUrl(path: string) {
|
|
84
|
+
return getTasksAppUrl(path);
|
|
85
|
+
}
|