Просмотр исходного кода

Merge pull request #22836 from overleaf/dp-filetree-typescript

Convert file-tree util files to typescript

GitOrigin-RevId: bdf8d0655a543a216f028bc8477c3ee47aba5566
David 1 год назад
Родитель
Сommit
2365d8c199

+ 3 - 3
services/web/frontend/js/features/file-tree/contexts/file-tree-actionable.tsx

@@ -60,7 +60,7 @@ const FileTreeActionableContext = createContext<
       canRename: boolean
       canCreate: boolean
       parentFolderId: string
-      selectedFileName: string | null
+      selectedFileName: string | null | undefined
       isDuplicate: (parentFolderId: string, name: string) => boolean
       startRenaming: any
       finishRenaming: any
@@ -320,7 +320,7 @@ export const FileTreeActionableProvider: FC = ({ children }) => {
 
   // moves entities. Tree is updated immediately and data are sync'd after.
   const finishMoving = useCallback(
-    (toFolderId, draggedEntityIds) => {
+    (toFolderId: string, draggedEntityIds: Set<string>) => {
       dispatch({ type: ACTION_TYPES.MOVING })
 
       // find entities and filter out no-ops and nested files
@@ -624,7 +624,7 @@ function validateCreate(
 
 function validateRename(
   fileTreeData: Folder,
-  found: { parentFolderId: string; path: string; type: string },
+  found: { parentFolderId: string; path: string[]; type: string },
   newName: string
 ) {
   if (!isCleanFilename(newName)) {

+ 2 - 1
services/web/frontend/js/features/file-tree/contexts/file-tree-draggable.tsx

@@ -15,6 +15,7 @@ import { useFileTreeActionable } from './file-tree-actionable'
 import { useFileTreeData } from '@/shared/context/file-tree-data-context'
 import { useFileTreeSelectable } from '../contexts/file-tree-selectable'
 import { isAcceptableFile } from '@/features/file-tree/util/is-acceptable-file'
+import { FileTreeFindResult } from '@/features/ide-react/types/file-tree'
 
 const DRAGGABLE_TYPE = 'ENTITY'
 export const FileTreeDraggableProvider: FC<{
@@ -171,7 +172,7 @@ function getDraggedTitle(
 }
 
 // Get all children folder ids of any of the dragged items.
-function getForbiddenFolderIds(draggedItems: Set<any>) {
+function getForbiddenFolderIds(draggedItems: Set<FileTreeFindResult>) {
   const draggedFoldersArray = Array.from(draggedItems)
     .filter(draggedItem => {
       return draggedItem.type === 'folder'

+ 3 - 3
services/web/frontend/js/features/file-tree/contexts/file-tree-selectable.tsx

@@ -10,7 +10,7 @@ import {
 } from 'react'
 import classNames from 'classnames'
 import _ from 'lodash'
-import { findInTree } from '../util/find-in-tree'
+import { findInTree, findInTreeOrThrow } from '../util/find-in-tree'
 import { useFileTreeData } from '../../../shared/context/file-tree-data-context'
 import { useProjectContext } from '../../../shared/context/project-context'
 import { useLayoutContext } from '../../../shared/context/layout-context'
@@ -177,7 +177,7 @@ export const FileTreeSelectableProvider: FC<{
     }
     const _selectedEntities = Array.from(selectedEntityIds)
       .map(id => findInTree(fileTreeData, id))
-      .filter(Boolean)
+      .filter(entity => entity !== null)
     onSelect(_selectedEntities)
     setSelectedEntities(_selectedEntities)
   }, [
@@ -282,7 +282,7 @@ export function useSelectableEntity(id: string, type: string) {
 
   const chooseView = useCallback(() => {
     for (const id of selectedEntityIds) {
-      const selectedEntity = findInTree(fileTreeData, id)
+      const selectedEntity = findInTreeOrThrow(fileTreeData, id)
 
       if (selectedEntity.type === 'doc') {
         return 'editor'

+ 1 - 1
services/web/frontend/js/features/file-tree/util/api.js → services/web/frontend/js/features/file-tree/util/api.ts

@@ -1,4 +1,4 @@
 import { postJSON } from '../../../infrastructure/fetch-json'
 
-export const refreshProjectMetadata = (projectId, entityId) =>
+export const refreshProjectMetadata = (projectId: string, entityId: string) =>
   postJSON(`/project/${projectId}/doc/${entityId}/metadata`)

+ 17 - 4
services/web/frontend/js/features/file-tree/util/count-in-tree.js → services/web/frontend/js/features/file-tree/util/count-in-tree.ts

@@ -1,6 +1,15 @@
 import getMeta from '@/utils/meta'
+import { Folder } from '../../../../../types/folder'
 
-export function countFiles(fileTreeData) {
+type FileCountStatus = 'success' | 'warning' | 'error'
+
+type FileCount = {
+  value: number
+  status: FileCountStatus
+  limit: number
+}
+
+export function countFiles(fileTreeData: Folder | undefined): 0 | FileCount {
   if (!fileTreeData) {
     return 0
   }
@@ -13,7 +22,11 @@ export function countFiles(fileTreeData) {
   return { value, status, limit }
 }
 
-function fileCountStatus(value, limit, range) {
+function fileCountStatus(
+  value: number,
+  limit: number,
+  range: number
+): FileCountStatus {
   if (value >= limit) {
     return 'error'
   }
@@ -26,8 +39,8 @@ function fileCountStatus(value, limit, range) {
 }
 
 // Copied and adapted from ProjectEntityMongoUpdateHandler
-function _countElements(rootFolder) {
-  function countFolder(folder) {
+function _countElements(rootFolder: Folder): number {
+  function countFolder(folder: Folder) {
     if (folder == null) {
       return 0
     }

+ 0 - 0
services/web/frontend/js/features/file-tree/util/file-collator.js → services/web/frontend/js/features/file-tree/util/file-collator.ts


+ 19 - 10
services/web/frontend/js/features/file-tree/util/find-in-tree.js → services/web/frontend/js/features/file-tree/util/find-in-tree.ts

@@ -1,20 +1,25 @@
 import OError from '@overleaf/o-error'
+import { Folder } from '../../../../../types/folder'
+import { FileTreeFindResult } from '@/features/ide-react/types/file-tree'
 
-export function findInTreeOrThrow(tree, id) {
+export function findInTreeOrThrow(tree: Folder, id: string) {
   const found = findInTree(tree, id)
   if (found) return found
   throw new OError('Entity not found in tree', { entityId: id })
 }
 
-export function findAllInTreeOrThrow(tree, ids) {
-  const list = new Set()
+export function findAllInTreeOrThrow(
+  tree: Folder,
+  ids: Set<string>
+): Set<FileTreeFindResult> {
+  const list: Set<FileTreeFindResult> = new Set()
   ids.forEach(id => {
     list.add(findInTreeOrThrow(tree, id))
   })
   return list
 }
 
-export function findAllFolderIdsInFolder(folder) {
+export function findAllFolderIdsInFolder(folder: Folder): Set<string> {
   const list = new Set([folder._id])
   for (const index in folder.folders) {
     const subFolder = folder.folders[index]
@@ -25,8 +30,8 @@ export function findAllFolderIdsInFolder(folder) {
   return list
 }
 
-export function findAllFolderIdsInFolders(folders) {
-  const list = new Set()
+export function findAllFolderIdsInFolders(folders: Set<Folder>): Set<string> {
+  const list: Set<string> = new Set()
   folders.forEach(folder => {
     findAllFolderIdsInFolder(folder).forEach(folderId => {
       list.add(folderId)
@@ -35,7 +40,11 @@ export function findAllFolderIdsInFolders(folders) {
   return list
 }
 
-export function findInTree(tree, id, path) {
+export function findInTree(
+  tree: Folder,
+  id: string,
+  path?: string[]
+): FileTreeFindResult | null {
   if (!path) {
     path = [tree._id]
   }
@@ -48,7 +57,7 @@ export function findInTree(tree, id, path) {
         parent: tree.docs,
         parentFolderId: tree._id,
         path,
-        index,
+        index: Number(index),
       }
     }
   }
@@ -62,7 +71,7 @@ export function findInTree(tree, id, path) {
         parent: tree.fileRefs,
         parentFolderId: tree._id,
         path,
-        index,
+        index: Number(index),
       }
     }
   }
@@ -76,7 +85,7 @@ export function findInTree(tree, id, path) {
         parent: tree.folders,
         parentFolderId: tree._id,
         path,
-        index,
+        index: Number(index),
       }
     }
     const found = findInTree(folder, id, path.concat(folder._id))

+ 5 - 5
services/web/frontend/js/features/file-tree/util/icon-type-from-name.js → services/web/frontend/js/features/file-tree/util/icon-type-from-name.ts

@@ -1,16 +1,16 @@
 import { isBootstrap5 } from '@/features/utils/bootstrap-5'
 
-export default function iconTypeFromName(name) {
+export default function iconTypeFromName(name: string): string {
   let ext = name.split('.').pop()
   ext = ext ? ext.toLowerCase() : ext
 
-  if (['png', 'pdf', 'jpg', 'jpeg', 'gif'].includes(ext)) {
+  if (ext && ['png', 'pdf', 'jpg', 'jpeg', 'gif'].includes(ext)) {
     return 'image'
-  } else if (['csv', 'xls', 'xlsx'].includes(ext)) {
+  } else if (ext && ['csv', 'xls', 'xlsx'].includes(ext)) {
     return isBootstrap5() ? 'table_chart' : 'table'
-  } else if (['py', 'r'].includes(ext)) {
+  } else if (ext && ['py', 'r'].includes(ext)) {
     return isBootstrap5() ? 'code' : 'file-text'
-  } else if (['bib'].includes(ext)) {
+  } else if (ext && ['bib'].includes(ext)) {
     return isBootstrap5() ? 'menu_book' : 'book'
   } else {
     return isBootstrap5() ? 'description' : 'file'

+ 3 - 3
services/web/frontend/js/features/file-tree/util/is-name-unique-in-folder.ts

@@ -1,4 +1,4 @@
-import { findInTree } from '../util/find-in-tree'
+import { findInTreeOrThrow } from '../util/find-in-tree'
 import { Folder } from '../../../../../types/folder'
 import { Doc } from '../../../../../types/doc'
 import { FileRef } from '../../../../../types/file-ref'
@@ -20,7 +20,7 @@ export function findFileByNameInFolder(
   name: string
 ): Doc | FileRef | undefined {
   if (tree._id !== parentFolderId) {
-    tree = findInTree(tree, parentFolderId).entity
+    tree = findInTreeOrThrow(tree, parentFolderId).entity as Folder
   }
 
   return (
@@ -35,7 +35,7 @@ export function findFolderByNameInFolder(
   name: string
 ): Folder | undefined {
   if (tree._id !== parentFolderId) {
-    tree = findInTree(tree, parentFolderId).entity
+    tree = findInTreeOrThrow(tree, parentFolderId).entity as Folder
   }
 
   return tree.folders.find(entity => entity.name === name)

+ 5 - 5
services/web/frontend/js/features/file-tree/util/safe-path.js → services/web/frontend/js/features/file-tree/util/safe-path.ts

@@ -58,7 +58,7 @@ prototype\
 
 const MAX_PATH = 1024 // Maximum path length, in characters. This is fairly arbitrary.
 
-export function clean(filename) {
+export function clean(filename: string): string {
   filename = filename.replace(BADCHAR_RX, '_')
   // for BADFILE_RX replace any matches with an equal number of underscores
   filename = filename.replace(BADFILE_RX, match =>
@@ -69,7 +69,7 @@ export function clean(filename) {
   return filename
 }
 
-export function isCleanFilename(filename) {
+export function isCleanFilename(filename: string): boolean {
   return (
     isAllowedLength(filename) &&
     !filename.match(BADCHAR_RX) &&
@@ -77,13 +77,13 @@ export function isCleanFilename(filename) {
   )
 }
 
-export function isBlockedFilename(filename) {
+export function isBlockedFilename(filename: string): boolean {
   return BLOCKEDFILE_RX.test(filename)
 }
 
 // returns whether a full path is 'clean' - e.g. is a full or relative path
 // that points to a file, and each element passes the rules in 'isCleanFilename'
-export function isCleanPath(path) {
+export function isCleanPath(path: string): boolean {
   const elements = path.split('/')
 
   const lastElementIsEmpty = elements[elements.length - 1].length === 0
@@ -105,6 +105,6 @@ export function isCleanPath(path) {
   return true
 }
 
-export function isAllowedLength(pathname) {
+export function isAllowedLength(pathname: string): boolean {
   return pathname.length > 0 && pathname.length <= MAX_PATH
 }

+ 26 - 5
services/web/frontend/js/features/file-tree/util/sync-mutation.js → services/web/frontend/js/features/file-tree/util/sync-mutation.ts

@@ -1,6 +1,11 @@
 import { postJSON, deleteJSON } from '../../../infrastructure/fetch-json'
 
-export function syncRename(projectId, entityType, entityId, newName) {
+export function syncRename(
+  projectId: string,
+  entityType: string,
+  entityId: string,
+  newName: string
+) {
   return postJSON(
     `/project/${projectId}/${getEntityPathName(entityType)}/${entityId}/rename`,
     {
@@ -11,13 +16,22 @@ export function syncRename(projectId, entityType, entityId, newName) {
   )
 }
 
-export function syncDelete(projectId, entityType, entityId) {
+export function syncDelete(
+  projectId: string,
+  entityType: string,
+  entityId: string
+) {
   return deleteJSON(
     `/project/${projectId}/${getEntityPathName(entityType)}/${entityId}`
   )
 }
 
-export function syncMove(projectId, entityType, entityId, toFolderId) {
+export function syncMove(
+  projectId: string,
+  entityType: string,
+  entityId: string,
+  toFolderId: string
+) {
   return postJSON(
     `/project/${projectId}/${getEntityPathName(entityType)}/${entityId}/move`,
     {
@@ -28,7 +42,14 @@ export function syncMove(projectId, entityType, entityId, toFolderId) {
   )
 }
 
-export function syncCreateEntity(projectId, parentFolderId, newEntityData) {
+export function syncCreateEntity(
+  projectId: string,
+  parentFolderId: string,
+  newEntityData: {
+    endpoint: 'doc' | 'folder' | 'linked-file'
+    [key: string]: unknown
+  }
+) {
   const { endpoint, ...newEntity } = newEntityData
   return postJSON(`/project/${projectId}/${endpoint}`, {
     body: {
@@ -38,6 +59,6 @@ export function syncCreateEntity(projectId, parentFolderId, newEntityData) {
   })
 }
 
-function getEntityPathName(entityType) {
+function getEntityPathName(entityType: string) {
   return entityType === 'fileRef' ? 'file' : entityType
 }

+ 1 - 1
services/web/frontend/js/features/ide-react/types/file-tree.ts

@@ -9,7 +9,7 @@ interface BaseFileTreeFindResult<T> {
   entity: T
   parent: T[]
   parentFolderId: string
-  path: string
+  path: string[]
   index: number
 }