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

Commit 2943419

Browse files
committed
Support dropped text files as new docs
1 parent 6a3002d commit 2943419

3 files changed

Lines changed: 140 additions & 9 deletions

File tree

src/cloud/components/ContentManager/index.tsx

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ const ContentManager = ({
4242
folders,
4343
workspacesMap,
4444
currentUserIsCoreMember,
45+
currentWorkspaceId,
46+
currentFolderId,
4547
page,
4648
}: ContentManagerProps) => {
4749
const { preferences, setPreferences } = usePreferences()
@@ -118,8 +120,12 @@ const ContentManager = ({
118120
[setPreferences]
119121
)
120122

121-
const { dropInDocOrFolder, saveDocTransferData, clearDragTransferData } =
122-
useCloudDnd()
123+
const {
124+
dropFilesAsDocs,
125+
dropInDocOrFolder,
126+
saveDocTransferData,
127+
clearDragTransferData,
128+
} = useCloudDnd()
123129

124130
const onDragStartDoc = useCallback(
125131
(event: any, doc: SerializedDocWithSupplemental) => {
@@ -145,8 +151,28 @@ const ContentManager = ({
145151
[clearDragTransferData]
146152
)
147153

154+
const onDragOverFiles = useCallback((event: React.DragEvent) => {
155+
if (event.dataTransfer.types.includes('Files')) {
156+
event.preventDefault()
157+
}
158+
}, [])
159+
160+
const onDropFiles = useCallback(
161+
(event: React.DragEvent) => {
162+
if (currentWorkspaceId == null) {
163+
return
164+
}
165+
166+
dropFilesAsDocs(event, team, {
167+
workspaceId: currentWorkspaceId,
168+
parentFolderId: currentFolderId,
169+
})
170+
},
171+
[currentFolderId, currentWorkspaceId, dropFilesAsDocs, team]
172+
)
173+
148174
return (
149-
<Container>
175+
<Container onDragOver={onDragOverFiles} onDrop={onDropFiles}>
150176
<Scroller className='cm__scroller'>
151177
<StyledContentManagerHeader>
152178
<div className='header__left' />

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

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { useCallback } from 'react'
2-
import { UpdateDocRequestBody } from '../../../api/teams/docs'
2+
import {
3+
createDoc,
4+
CreateDocRequestBody,
5+
UpdateDocRequestBody,
6+
} from '../../../api/teams/docs'
37
import { UpdateFolderRequestBody } from '../../../api/teams/folders'
48
import { moveResource } from '../../../api/teams/resources'
59
import {
@@ -20,23 +24,107 @@ import {
2024
} from '../../utils/patterns'
2125
import { SerializedFolderWithBookmark } from '../../../interfaces/db/folder'
2226
import { SerializedDocWithSupplemental } from '../../../interfaces/db/doc'
27+
import { SerializedTeam } from '../../../interfaces/db/team'
2328
import { SidebarDragState } from '../../../../design/lib/dnd'
2429
import { useToast } from '../../../../design/lib/stores/toast'
2530
import { getMapFromEntityArray } from '../../../../design/lib/utils/array'
2631

32+
const textFileExtensions = new Set(['.md', '.txt', '.html', '.htm'])
33+
34+
function getDroppedFiles(event: any) {
35+
return Array.from<File>(event.dataTransfer?.files || []).filter((file) => {
36+
const lowerCaseName = file.name.toLowerCase()
37+
return Array.from(textFileExtensions).some((extension) =>
38+
lowerCaseName.endsWith(extension)
39+
)
40+
})
41+
}
42+
43+
function getDocTitleFromFileName(fileName: string) {
44+
const matchingExtension = Array.from(textFileExtensions).find((extension) =>
45+
fileName.toLowerCase().endsWith(extension)
46+
)
47+
48+
if (matchingExtension == null) {
49+
return fileName
50+
}
51+
52+
return fileName.slice(0, -matchingExtension.length) || fileName
53+
}
54+
55+
function readTextFile(file: File) {
56+
return new Promise<string>((resolve, reject) => {
57+
const reader = new FileReader()
58+
reader.onload = () => resolve(String(reader.result || ''))
59+
reader.onerror = () => reject(reader.error)
60+
reader.readAsText(file)
61+
})
62+
}
63+
2764
export function useCloudDnd() {
2865
const {
2966
updateFoldersMap,
3067
updateDocsMap,
3168
updateWorkspacesMap,
69+
updateParentFolderOfDoc,
70+
updateParentWorkspaceOfDoc,
3271
setCurrentPath,
3372
} = useNav()
3473
const { pageDoc, pageFolder } = usePage()
3574
const { pushApiErrorMessage } = useToast()
3675

76+
const dropFilesAsDocs = useCallback(
77+
async (
78+
event: any,
79+
team: SerializedTeam,
80+
destination: Pick<CreateDocRequestBody, 'workspaceId' | 'parentFolderId'>
81+
) => {
82+
const files = getDroppedFiles(event)
83+
if (files.length === 0) {
84+
return false
85+
}
86+
87+
event.preventDefault()
88+
event.stopPropagation()
89+
90+
try {
91+
for (const file of files) {
92+
const content = await readTextFile(file)
93+
const { doc } = await createDoc(
94+
{ id: team.id },
95+
{
96+
...destination,
97+
title: getDocTitleFromFileName(file.name),
98+
content,
99+
}
100+
)
101+
102+
updateDocsMap([doc.id, doc])
103+
104+
if (doc.parentFolder != null) {
105+
updateParentFolderOfDoc(doc)
106+
} else if (doc.workspace != null) {
107+
updateParentWorkspaceOfDoc(doc)
108+
}
109+
}
110+
} catch (error) {
111+
pushApiErrorMessage(error)
112+
}
113+
114+
return true
115+
},
116+
[
117+
pushApiErrorMessage,
118+
updateDocsMap,
119+
updateParentFolderOfDoc,
120+
updateParentWorkspaceOfDoc,
121+
]
122+
)
123+
37124
const dropInWorkspace = useCallback(
38125
async (
39126
event: any,
127+
team: SerializedTeam,
40128
workspaceId: string,
41129
updateFolder: (
42130
folder: FolderDataTransferItem,
@@ -47,6 +135,11 @@ export function useCloudDnd() {
47135
body: UpdateDocRequestBody
48136
) => Promise<void>
49137
) => {
138+
const droppedFiles = await dropFilesAsDocs(event, team, { workspaceId })
139+
if (droppedFiles) {
140+
return
141+
}
142+
50143
const draggedResource = getDraggedResource(event)
51144
if (draggedResource === null) {
52145
return
@@ -69,7 +162,7 @@ export function useCloudDnd() {
69162
})
70163
}
71164
},
72-
[]
165+
[dropFilesAsDocs]
73166
)
74167

75168
const dropInDocOrFolder = useCallback(
@@ -171,6 +264,7 @@ export function useCloudDnd() {
171264
}, [])
172265

173266
return {
267+
dropFilesAsDocs,
174268
dropInWorkspace,
175269
dropInDocOrFolder,
176270
saveFolderTransferData,

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

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ export function useCloudSidebarTree() {
109109
} = useSidebarCollapse()
110110

111111
const {
112+
dropFilesAsDocs,
112113
dropInDocOrFolder,
113114
dropInWorkspace,
114115
saveFolderTransferData,
@@ -263,7 +264,7 @@ export function useCloudSidebarTree() {
263264
? {
264265
dropIn: true,
265266
onDrop: (event: any) =>
266-
dropInWorkspace(event, wp.id, updateFolder, updateDoc),
267+
dropInWorkspace(event, team, wp.id, updateFolder, updateDoc),
267268
controls: [
268269
{
269270
icon: mdiTextBoxPlus,
@@ -339,15 +340,24 @@ export function useCloudSidebarTree() {
339340
const coreRestrictedFeatures: Partial<CloudTreeItem> =
340341
currentUserIsCoreMember
341342
? {
342-
onDrop: (event: any, position: SidebarDragState) =>
343-
dropInDocOrFolder(
343+
onDrop: async (event: any, position: SidebarDragState) => {
344+
const droppedFiles = await dropFilesAsDocs(event, team, {
345+
parentFolderId: folder.id,
346+
workspaceId: folder.workspaceId,
347+
})
348+
if (droppedFiles) {
349+
return
350+
}
351+
352+
return dropInDocOrFolder(
344353
event,
345354
{
346355
type: 'folder',
347356
resource: folderToDataTransferItem(folder),
348357
},
349358
position
350-
),
359+
)
360+
},
351361
onDragStart: (event: any) => {
352362
saveFolderTransferData(event, folder)
353363
},
@@ -941,6 +951,7 @@ export function useCloudSidebarTree() {
941951
treeSendingMap,
942952
sideBarOpenedFolderIdsSet,
943953
dropInDocOrFolder,
954+
dropFilesAsDocs,
944955
saveFolderTransferData,
945956
clearDragTransferData,
946957
toggleFolderBookmark,

0 commit comments

Comments
 (0)