Skip to content
This repository was archived by the owner on May 12, 2026. It is now read-only.

Commit 6bc4a43

Browse files
mrzhangkrislaomo
authored andcommitted
feat: drag-and-drop .md/.txt/.html files to workspace creates new docs
- Added dropFilesInWorkspace handler to useCloudDnd hook - Modified dropInWorkspace to detect file drops vs resource drops - Workspace onDrop handler now routes files to dropFilesInWorkspace - createDoc receives file content as initial doc body (for .md/.txt/.html) - Closes #1151 (IssueHunt bounty)
1 parent 6a3002d commit 6bc4a43

2 files changed

Lines changed: 73 additions & 2 deletions

File tree

src/cloud/lib/hooks/sidebar/useCloudDnd.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ import { SerializedDocWithSupplemental } from '../../../interfaces/db/doc'
2323
import { SidebarDragState } from '../../../../design/lib/dnd'
2424
import { useToast } from '../../../../design/lib/stores/toast'
2525
import { getMapFromEntityArray } from '../../../../design/lib/utils/array'
26+
import { SerializedTeam } from '../../../interfaces/db/team'
27+
28+
const SUPPORTED_FILE_EXTENSIONS = ['.md', '.txt', '.html']
29+
const FILE_ENCODING = 'utf-8'
2630

2731
export function useCloudDnd() {
2832
const {
@@ -47,11 +51,17 @@ export function useCloudDnd() {
4751
body: UpdateDocRequestBody
4852
) => Promise<void>
4953
) => {
54+
const files = event.dataTransfer?.files
55+
if (files != null && files.length > 0) {
56+
return 'files'
57+
}
58+
5059
const draggedResource = getDraggedResource(event)
5160
if (draggedResource === null) {
5261
return
5362
}
5463

64+
5565
if (draggedResource.type === 'folder') {
5666
const folder = draggedResource.resource
5767
await updateFolder(folder, {
@@ -72,6 +82,58 @@ export function useCloudDnd() {
7282
[]
7383
)
7484

85+
const dropFilesInWorkspace = useCallback(
86+
async (
87+
event: any,
88+
workspaceId: string,
89+
team: SerializedTeam,
90+
createDoc: (
91+
team: SerializedTeam,
92+
body: { workspaceId: string; title: string; content?: string }
93+
) => Promise<{ id: string }>
94+
) => {
95+
const files = event.dataTransfer?.files
96+
if (files == null || files.length === 0) {
97+
return
98+
}
99+
100+
for (let i = 0; i < files.length; i++) {
101+
const file = files[i]
102+
const ext = getFileExtension(file.name)
103+
if (!SUPPORTED_FILE_EXTENSIONS.includes(ext)) {
104+
continue
105+
}
106+
107+
try {
108+
const content = await readFileAsText(file)
109+
const title = file.name.replace(ext, '')
110+
await createDoc(team, {
111+
workspaceId,
112+
title,
113+
content,
114+
})
115+
} catch (err) {
116+
console.warn('Failed to create doc from file:', file.name, err)
117+
}
118+
}
119+
},
120+
[]
121+
)
122+
123+
function getFileExtension(filename: string): string {
124+
const lastDot = filename.lastIndexOf('.')
125+
return lastDot >= 0 ? filename.slice(lastDot).toLowerCase() : ''
126+
}
127+
128+
async function readFileAsText(file: File): Promise<string> {
129+
return new Promise((resolve, reject) => {
130+
const reader = new FileReader()
131+
reader.onload = () => resolve(reader.result as string)
132+
reader.onerror = () => reject(reader.error)
133+
reader.readAsText(file, FILE_ENCODING)
134+
})
135+
}
136+
75137
const dropInDocOrFolder = useCallback(
76138
async (
77139
event: any,
@@ -173,6 +235,7 @@ export function useCloudDnd() {
173235
return {
174236
dropInWorkspace,
175237
dropInDocOrFolder,
238+
dropFilesInWorkspace,
176239
saveFolderTransferData,
177240
saveDocTransferData,
178241
clearDragTransferData,

src/cloud/lib/hooks/sidebar/useCloudSidebarTree.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ export function useCloudSidebarTree() {
111111
const {
112112
dropInDocOrFolder,
113113
dropInWorkspace,
114+
dropFilesInWorkspace,
114115
saveFolderTransferData,
115116
saveDocTransferData,
116117
clearDragTransferData,
@@ -262,8 +263,14 @@ export function useCloudSidebarTree() {
262263
currentUserIsCoreMember
263264
? {
264265
dropIn: true,
265-
onDrop: (event: any) =>
266-
dropInWorkspace(event, wp.id, updateFolder, updateDoc),
266+
onDrop: (event: any) => {
267+
const files = event.dataTransfer?.files
268+
if (files != null && files.length > 0) {
269+
dropFilesInWorkspace(event, wp.id, team, createDoc)
270+
} else {
271+
dropInWorkspace(event, wp.id, updateFolder, updateDoc)
272+
}
273+
},
267274
controls: [
268275
{
269276
icon: mdiTextBoxPlus,
@@ -1117,4 +1124,5 @@ type CloudTreeItem = {
11171124
onDragStart?: (event: any) => void
11181125
onDrop?: (event: any, position?: SidebarDragState) => void
11191126
onDragEnd?: (event: any) => void
1127+
onDropFiles?: (event: any) => void
11201128
}

0 commit comments

Comments
 (0)