mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-06-04 14:49:01 +02:00
Merge pull request #22836 from overleaf/dp-filetree-typescript
Convert file-tree util files to typescript GitOrigin-RevId: bdf8d0655a543a216f028bc8477c3ee47aba5566
This commit is contained in:
@@ -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)) {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
+19
-10
@@ -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
@@ -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'
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
@@ -9,7 +9,7 @@ interface BaseFileTreeFindResult<T> {
|
||||
entity: T
|
||||
parent: T[]
|
||||
parentFolderId: string
|
||||
path: string
|
||||
path: string[]
|
||||
index: number
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user