From 79e3696fd213c5b343c0162f87b17347d51ac882 Mon Sep 17 00:00:00 2001 From: hamedmp Date: Tue, 29 Apr 2025 14:13:32 +0200 Subject: [PATCH 1/7] fix file tree icons --- src/app/(ide)/@editor/default.tsx | 2 +- src/app/(ide)/ide/layout.tsx | 9 +- .../file-tree/components/directory-node.tsx | 65 +- src/lib/file-utils.tsx | 927 +++++++++++++++++- 4 files changed, 925 insertions(+), 78 deletions(-) diff --git a/src/app/(ide)/@editor/default.tsx b/src/app/(ide)/@editor/default.tsx index 1c36d3d..6a40406 100644 --- a/src/app/(ide)/@editor/default.tsx +++ b/src/app/(ide)/@editor/default.tsx @@ -2,4 +2,4 @@ export default function Default() { return null; -} +} \ No newline at end of file diff --git a/src/app/(ide)/ide/layout.tsx b/src/app/(ide)/ide/layout.tsx index 1b93aaa..010000f 100644 --- a/src/app/(ide)/ide/layout.tsx +++ b/src/app/(ide)/ide/layout.tsx @@ -25,10 +25,13 @@ export default function IDELayout({ terminal: ReactNode; agent: ReactNode; }) { - const { loadDirectory } = useFileStore(useShallow(state => ({ + const { loadDirectory, filteredFiles, searchQuery } = useFileStore(useShallow(state => ({ loadDirectory: state.loadDirectory, + filteredFiles: state.filteredFiles, + searchQuery: state.searchQuery, }))); + const { projects, activeProject, removeProject, setActiveProject } = useIDEStore(useShallow(state => ({ projects: state.projects, @@ -115,6 +118,10 @@ export default function IDELayout({ +
+ {filteredFiles.length} items + {searchQuery && " (filtered)"} +
); } diff --git a/src/components/file-tree/components/directory-node.tsx b/src/components/file-tree/components/directory-node.tsx index 6519c34..14d8871 100644 --- a/src/components/file-tree/components/directory-node.tsx +++ b/src/components/file-tree/components/directory-node.tsx @@ -6,7 +6,7 @@ import { ContextMenuSeparator, ContextMenuTrigger, } from "@/components/ui/context-menu"; -import { getFileIcon } from "@/lib/file-utils"; +import { getFileIcon, getFolderIcon } from "@/lib/file-utils"; import { cn } from "@/lib/utils"; import { useDirectoryTreeStore } from "@/store/directory-tree-store"; import { useFileStore } from "@/store/file-store"; @@ -17,7 +17,6 @@ import { ChevronRight, FilePlus, Folder, - FolderOpen, FolderPlus, Pencil, } from "lucide-react"; @@ -60,40 +59,40 @@ export const DirectoryNode = memo( // Get directory data from the tree structure const dirData = useFileStore( - useShallow(state => { + useShallow((state) => { // Get the directory's path segments - const pathParts = directory.path.split('/').filter(Boolean); - + const pathParts = directory.path.split("/").filter(Boolean); + // Start at the root of the tree let node = state.tree; - + // If we're at root or tree is not available, return minimal data if (pathParts.length === 0 || !node) { - return { + return { node, isIgnored: state.isIgnored, - getFileStatus: state.getFileStatus + getFileStatus: state.getFileStatus, }; } - + // Traverse the tree to find this directory's node for (const part of pathParts) { if (!node || !node.children[part]) { - return { + return { node: null, isIgnored: state.isIgnored, - getFileStatus: state.getFileStatus + getFileStatus: state.getFileStatus, }; } node = node.children[part]; } - - return { + + return { node, isIgnored: state.isIgnored, - getFileStatus: state.getFileStatus + getFileStatus: state.getFileStatus, }; - }) + }), ); const handleToggle = useCallback(async () => { @@ -133,10 +132,14 @@ export const DirectoryNode = memo( // Extract children from the tree node for (const [name, childNode] of Object.entries(dirData.node.children)) { // Skip ignored files unless showIgnoredFiles is true - if (childNode.file && !showIgnoredFiles && dirData.isIgnored(childNode.file.path)) { + if ( + childNode.file && + !showIgnoredFiles && + dirData.isIgnored(childNode.file.path) + ) { continue; } - + if (childNode.file) { if (childNode.file.isDirectory) { childDirs.push(childNode.file); @@ -158,13 +161,13 @@ export const DirectoryNode = memo( // If we don't have the tree data yet, use a fallback to keep things working // This can happen during initial load or if we haven't built the tree yet const shouldFallbackToLegacyMethod = !dirData.node && allFiles.length > 0; - + // Legacy method as fallback - only used if tree data is not available const legacyChildrenData = useMemo(() => { if (!shouldFallbackToLegacyMethod) { return { childDirs: [], childFiles: [] }; } - + const childDirs: typeof allFiles = []; const childFiles: typeof allFiles = []; const basePathWithSlash = `${directory.path}/`; @@ -172,7 +175,7 @@ export const DirectoryNode = memo( for (const file of allFiles) { if (file.path === directory.path) continue; if (!file.path.startsWith(basePathWithSlash)) continue; - + // Skip ignored files unless showIgnoredFiles is true if (!showIgnoredFiles && isIgnored(file.path)) continue; @@ -206,11 +209,21 @@ export const DirectoryNode = memo( childFiles.sort((a, b) => a.name.localeCompare(b.name)); return { childDirs, childFiles }; - }, [shouldFallbackToLegacyMethod, allFiles, directory.path, isIgnored, showIgnoredFiles]); + }, [ + shouldFallbackToLegacyMethod, + allFiles, + directory.path, + isIgnored, + showIgnoredFiles, + ]); // Use the appropriate data source - const finalChildDirs = shouldFallbackToLegacyMethod ? legacyChildrenData.childDirs : childDirs; - const finalChildFiles = shouldFallbackToLegacyMethod ? legacyChildrenData.childFiles : childFiles; + const finalChildDirs = shouldFallbackToLegacyMethod + ? legacyChildrenData.childDirs + : childDirs; + const finalChildFiles = shouldFallbackToLegacyMethod + ? legacyChildrenData.childFiles + : childFiles; return ( @@ -243,10 +256,8 @@ export const DirectoryNode = memo(
{loading ? (
- ) : isExpanded ? ( - ) : ( - + getFolderIcon(isExpanded, directory.name) )}
{isRenaming ? ( @@ -398,7 +409,7 @@ export const DirectoryNode = memo( // Optimizing memo comparison by checking exactly what matters return ( prev.directory.path === next.directory.path && - prev.allFiles === next.allFiles && + prev.allFiles === next.allFiles && prev.selectedFile?.path === next.selectedFile?.path && prev.showIgnoredFiles === next.showIgnoredFiles && prev.renameTarget === next.renameTarget && diff --git a/src/lib/file-utils.tsx b/src/lib/file-utils.tsx index 171df75..f9c83de 100644 --- a/src/lib/file-utils.tsx +++ b/src/lib/file-utils.tsx @@ -1,11 +1,234 @@ import { FileBadge } from "lucide-react"; import Image from "next/image"; +// Helper function to get icon for files without extension +const getIconForSpecialFile = (fileName: string) => { + const lowerFileName = fileName.toLowerCase(); + + // Common special files + switch (fileName) { + case "LICENSE": + case "LICENSE.md": + case "LICENSE.txt": + return ( + License file + ); + case "README": + case "README.md": + return ( + Readme file + ); + case "CHANGELOG": + case "CHANGELOG.md": + return ( + Changelog file + ); + case "CONTRIBUTING": + case "CONTRIBUTING.md": + return ( + Contributing file + ); + case "CODE_OF_CONDUCT": + case "CODE_OF_CONDUCT.md": + return ( + Code of conduct file + ); + case ".env": + case ".env.local": + case ".env.development": + case ".env.production": + case ".env.example": + return ( + Environment file + ); + case ".gitignore": + case ".gitconfig": + case ".gitattributes": + return ( + Git file + ); + case "package.json": + return ( + Package file + ); + case "package-lock.json": + case "yarn.lock": + case "pnpm-lock.yaml": + return ( + Lock file + ); + case "tsconfig.json": + case "tsconfig.base.json": + return ( + TypeScript config + ); + case "next.config.js": + case "next.config.mjs": + return ( + Next.js config + ); + case "vite.config.ts": + case "vite.config.js": + return ( + Vite config + ); + case "tailwind.config.js": + case "tailwind.config.ts": + return ( + Tailwind config + ); + case "postcss.config.js": + return ( + PostCSS config + ); + case "prettier.config.js": + case ".prettierrc": + case ".prettierrc.json": + case ".prettierrc.js": + return ( + Prettier config + ); + case "eslint.config.js": + case ".eslintrc": + case ".eslintrc.js": + case ".eslintrc.json": + return ( + ESLint config + ); + case "jest.config.js": + case "jest.config.ts": + return ( + Jest config + ); + case "vitest.config.ts": + case "vitest.config.js": + return ( + Vitest config + ); + case "Dockerfile": + return ( + Dockerfile + ); + case "docker-compose.yml": + case "docker-compose.yaml": + return ( + Docker Compose + ); + case "Makefile": + return ( + Makefile + ); + default: + if (lowerFileName.includes("dockerfile")) { + return ( + Docker file + ); + } + return null; + } +}; + // Get file icon based on extension export const getFileIcon = (fileName: string) => { + // First check for special files without extensions or with specific names + const specialFileIcon = getIconForSpecialFile(fileName); + if (specialFileIcon) { + return specialFileIcon; + } + const ext = fileName.split(".").pop()?.toLowerCase(); - // First try to match with available icon in the public/icons directory + // Then try to match with available icon in the public/icons directory switch (ext) { // JavaScript/TypeScript files case "js": @@ -44,9 +267,29 @@ export const getFileIcon = (fileName: string) => { height={18} /> ); + case "mjs": + case "cjs": + return ( + JavaScript module + ); + case "d.ts": + return ( + TypeScript definition + ); // Web files case "html": + case "htm": return ( HTML file ); @@ -59,6 +302,28 @@ export const getFileIcon = (fileName: string) => { return ( SASS file ); + case "less": + return ( + Less file + ); + case "styl": + return ( + Stylus file + ); + case "postcss": + return ( + PostCSS file + ); // Config files case "json": @@ -75,9 +340,21 @@ export const getFileIcon = (fileName: string) => { return ( TOML file ); + case "ini": + case "conf": + case "config": + return ( + Config file + ); // Documentation case "md": + case "mdx": return ( { return ( Text file ); + case "pdf": + return ( + PDF file + ); + case "doc": + case "docx": + return ( + Word file + ); + case "xls": + case "xlsx": + return ( + Excel file + ); + case "ppt": + case "pptx": + return ( + PowerPoint file + ); // Images case "svg": @@ -100,6 +401,9 @@ export const getFileIcon = (fileName: string) => { case "jpg": case "jpeg": case "gif": + case "bmp": + case "ico": + case "webp": return ( { /> ); + // Media + case "mp3": + case "wav": + case "ogg": + case "flac": + return ( + Audio file + ); + case "mp4": + case "webm": + case "mkv": + case "avi": + case "mov": + return ( + Video file + ); + + // Archives + case "zip": + case "rar": + case "7z": + case "tar": + case "gz": + return ( + Archive file + ); + // Languages case "go": + case "mod": return Go file; case "py": return ( @@ -131,20 +463,50 @@ export const getFileIcon = (fileName: string) => { PHP file ); case "java": + case "class": + case "jar": return ( Java file ); case "c": return C file; + case "h": + return ( + Header file + ); case "cpp": + case "cc": + case "cxx": return ( C++ file ); + case "hpp": + case "hxx": + return ( + C++ Header file + ); + case "cs": + return ( + C# file + ); case "sh": case "bash": + case "zsh": + case "fish": + case "ksh": + case "csh": + case "tcsh": + case "ps1": // PowerShell + case "psm1": // PowerShell module + case "psd1": // PowerShell data return ( Shell script { return ( Vue file ); - case "prisma": + case "svelte": return ( Prisma file ); - case "graphql": + case "prisma": return ( GraphQL file ); - case "docker": - case "dockerfile": + case "graphql": + case "gql": return ( Docker file ); - case "terraform": case "tf": + case "tfvars": + case "hcl": return ( { /> ); case "xml": + case "xaml": return ( XML file ); @@ -213,41 +577,100 @@ export const getFileIcon = (fileName: string) => { return ( Swift file ); - case "env": + case "kt": + case "kts": return ( - Settings file + Kotlin file ); - case "gitignore": + case "dart": return ( - Git file + Dart file ); - case "gitconfig": + case "ex": + case "exs": return ( - Git config file + Elixir file ); - case "gitattributes": + case "elm": return ( - Git attributes file + Elm file ); - case "LICENSE": + case "erl": + return ( + Erlang file + ); + case "fs": + case "fsx": return ( - License file + F# file ); - case "README": - return Readme file; - case "CONTRIBUTING": - return Contributing file; - case "CHANGELOG": - return Changelog file; - case "CODE_OF_CONDUCT": - return Code of conduct file; - case "ISSUE_TEMPLATE": - return Issue template file; - case "PULL_REQUEST_TEMPLATE": - return Pull request template file; - - - + case "hs": + return ( + Haskell file + ); + case "lua": + return ( + Lua file + ); + case "r": + return R file; + case "scala": + return ( + Scala file + ); + case "pl": + case "pm": + return ( + Perl file + ); + case "rkt": + return ( + Racket file + ); + case "clj": + case "cljs": + return ( + Clojure file + ); + case "vim": + return ( + Vim file + ); + case "astro": + return ( + Astro file + ); + // Fallback to Lucide icons for types without matching SVG icons default: return ; @@ -292,35 +715,441 @@ export const formatDate = (date: Date): string => { // Is this a text file that can be displayed? export const isTextFile = (fileName: string) => { const textExtensions = [ + // Documentation "txt", "md", + "mdx", + "rst", + "tex", + "adoc", + "asc", + "asciidoc", + + // Web + "html", + "htm", + "xhtml", + "css", + "scss", + "sass", + "less", + "styl", "js", "jsx", "ts", "tsx", - "css", - "scss", - "html", + "mjs", + "cjs", "json", "jsonc", + "vue", + "svelte", + "astro", + + // Config "toml", "yml", "yaml", "xml", - "svg", + "ini", + "conf", + "config", + "env", + "properties", + "prop", + "cfg", + + // Programming Languages "py", "rb", - "sh", - "bash", + "php", + "java", + "kt", + "kts", "c", - "cpp", "h", - "java", - "php", - "go", - "rust", + "cpp", + "hpp", + "cc", + "cxx", + "hxx", + "cs", "fs", + "fsx", + "go", + "rs", + "swift", + "sh", + "bash", + "zsh", + "fish", + "lua", + "tcl", + "pl", + "pm", + "t", + "r", + "scala", + "groovy", + "dart", + "ex", + "exs", + "erl", + "hrl", + "elm", + "hs", + "lhs", + "rkt", + "clj", + "cljs", + "vim", + "sql", + "graphql", + "gql", + + // Build/Config + "Dockerfile", + "docker-compose.yml", + "docker-compose.yaml", + "Makefile", + "cmake", + "ninja", + "gradle", + "pom", + "ivy", + + // Version Control + "gitignore", + "gitattributes", + "gitmodules", + + // Lock files + "lock", + "lockfile", + + // Other + "log", + "diff", + "patch", + ]; + + // First check for special files without extensions + const specialFiles = [ + "LICENSE", + "README", + "CHANGELOG", + "CONTRIBUTING", + "CODE_OF_CONDUCT", + "AUTHORS", + "MAINTAINERS", + "Dockerfile", + "Makefile", + "Vagrantfile", + ".env", + ".gitignore", + ".prettierrc", + ".eslintrc", ]; + + if (specialFiles.includes(fileName)) { + return true; + } + const ext = fileName.split(".").pop()?.toLowerCase(); return textExtensions.includes(ext || ""); }; + +// Get folder icon based on state and name +export const getFolderIcon = (isOpen: boolean, folderName?: string) => { + // Use default 'other' icon if no name provided + if (!folderName) { + return isOpen ? ( + Open folder + ) : ( + Folder + ); + } + + const name = folderName.toLowerCase(); + + // List derived from the ls command output + const specialFolders = [ + "admin", + "android", + "angular", + "animation", + "ansible", + "api", + "apollo", + "app", + "archive", + "astro", + "audio", + "aurelia", + "aws", + "azure-pipelines", + "base", + "batch", + "benchmark", + "bicep", + "bloc", + "bower", + "buildkite", + "cart", + "changesets", + "ci", + "circleci", + "class", + "client", + "cline", + "cloud-functions", + "cloudflare", + "cluster", + "cobol", + "command", + "components", + "config", + "connection", + "console", + "constant", + "container", + "content", + "context", + "contract", + "controller", + "core", + "coverage", + "css", + "custom", + "cypress", + "dart", + "database", + "debug", + "decorators", + "delta", + "desktop", + "directive", + "dist", + "docker", + "docs", + "download", + "drizzle", + "dump", + "element", + "enum", + "environment", + "error", + "event", + "examples", + "expo", + "export", + "fastlane", + "favicon", + "firebase", + "firestore", + "flow", + "flutter", + "font", + "forgejo", + "functions", + "gamemaker", + "generator", + "gh-workflows", + "git", + "gitea", + "github", + "gitlab", + "global", + "godot", + "gradle", + "graphql", + "guard", + "gulp", + "helm", + "helper", + "home", + "hook", + "husky", + "i18n", + "images", + "import", + "include", + "intellij", + "interface", + "ios", + "java", + "javascript", + "jinja", + "job", + "json", + "jupyter", + "keys", + "kubernetes", + "kusto", + "layout", + "lefthook", + "less", + "lib", + "linux", + "liquibase", + "log", + "lottie", + "lua", + "luau", + "macos", + "mail", + "mappings", + "markdown", + "mercurial", + "messages", + "meta", + "middleware", + "mjml", + "mobile", + "mock", + "mojo", + "moon", + "netlify", + "next", + "ngrx-store", + "node", + "nuxt", + "obsidian", + "other", + "packages", + "pdf", + "pdm", + "php", + "phpmailer", + "pipe", + "plastic", + "plugin", + "policy", + "powershell", + "prisma", + "private", + "project", + "proto", + "public", + "python", + "quasar", + "queue", + "react-components", + "redux-reducer", + "repository", + "resolver", + "resource", + "review", + "robot", + "routes", + "rules", + "rust", + "sandbox", + "sass", + "scala", + "scons", + "scripts", + "secure", + "seeders", + "server", + "serverless", + "shader", + "shared", + "snapcraft", + "snippet", + "src", + "src-tauri", + "stack", + "stencil", + "store", + "storybook", + "stylus", + "sublime", + "supabase", + "svelte", + "svg", + "syntax", + "target", + "taskfile", + "tasks", + "television", + "temp", + "template", + "terraform", + "test", + "theme", + "tools", + "trash", + "turborepo", + "typescript", + "ui", + "unity", + "update", + "upload", + "utils", + "vercel", + "verdaccio", + "video", + "views", + "vm", + "vscode", + "vue-directives", + "vue", + "vuepress", + "vuex-store", + "wakatime", + "webpack", + "windows", + "wordpress", + "yarn", + "zeabur", + ]; + + // Check if folder name exactly matches one of our special folders + if (specialFolders.includes(name)) { + const iconPath = `/icons/folder-${name}${isOpen ? "-open" : ""}.svg`; + const altText = `${isOpen ? "Open " : ""}${folderName} folder`; + // NOTE: We assume the corresponding open/closed icon exists based on the ls output + // A more robust solution might check file existence, but this is simpler for now. + return ( + {altText} + ); + } + + // Default folder icon for non-special folders (using 'other') + return isOpen ? ( + Open folder + ) : ( + Folder + ); +}; From d19f10b9e8db0eaabde53c116162b102f57bf615 Mon Sep 17 00:00:00 2001 From: hamedmp Date: Tue, 29 Apr 2025 14:39:34 +0200 Subject: [PATCH 2/7] tiptap markdown preview + sonner --- package.json | 1 + pnpm-lock.yaml | 39 +++++++++++++++++++ src/app/layout.tsx | 2 + .../file-monaco-editor/hooks/use-monaco.ts | 1 + src/components/tiptap-editor.tsx | 21 +++++----- 5 files changed, 53 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index b8ddf7f..def475c 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "socket.io-client": "^4.8.1", "sonner": "^2.0.3", "tailwind-merge": "^3.2.0", + "tiptap-markdown": "^0.8.10", "tw-animate-css": "^1.2.8", "utf-8-validate": "^6.0.5", "ws": "^8.18.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a66aba6..9f678ec 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -167,6 +167,9 @@ importers: tailwind-merge: specifier: ^3.2.0 version: 3.2.0 + tiptap-markdown: + specifier: ^0.8.10 + version: 0.8.10(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) tw-animate-css: specifier: ^1.2.8 version: 1.2.8 @@ -2020,18 +2023,27 @@ packages: '@types/keyv@3.1.4': resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} + '@types/linkify-it@3.0.5': + resolution: {integrity: sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==} + '@types/linkify-it@5.0.0': resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} '@types/lodash@4.17.16': resolution: {integrity: sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==} + '@types/markdown-it@13.0.9': + resolution: {integrity: sha512-1XPwR0+MgXLWfTn9gCsZ55AHOKW1WN+P9vr0PaQh5aerR9LLQXUbjfEAFhjmEmyoYFWAyuN2Mqkn40MZ4ukjBw==} + '@types/markdown-it@14.1.2': resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + '@types/mdurl@1.0.5': + resolution: {integrity: sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==} + '@types/mdurl@2.0.0': resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} @@ -3471,6 +3483,9 @@ packages: resolution: {integrity: sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + markdown-it-task-lists@2.1.1: + resolution: {integrity: sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==} + markdown-it@14.1.0: resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} hasBin: true @@ -4464,6 +4479,11 @@ packages: tippy.js@6.3.7: resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==} + tiptap-markdown@0.8.10: + resolution: {integrity: sha512-iDVkR2BjAqkTDtFX0h94yVvE2AihCXlF0Q7RIXSJPRSR5I0PA1TMuAg6FHFpmqTn4tPxJ0by0CK7PUMlnFLGEQ==} + peerDependencies: + '@tiptap/core': ^2.0.3 + tldts-core@6.1.86: resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} @@ -6560,10 +6580,17 @@ snapshots: dependencies: '@types/node': 20.17.30 + '@types/linkify-it@3.0.5': {} + '@types/linkify-it@5.0.0': {} '@types/lodash@4.17.16': {} + '@types/markdown-it@13.0.9': + dependencies: + '@types/linkify-it': 3.0.5 + '@types/mdurl': 1.0.5 + '@types/markdown-it@14.1.2': dependencies: '@types/linkify-it': 5.0.0 @@ -6573,6 +6600,8 @@ snapshots: dependencies: '@types/unist': 3.0.3 + '@types/mdurl@1.0.5': {} + '@types/mdurl@2.0.0': {} '@types/ms@2.1.0': {} @@ -8262,6 +8291,8 @@ snapshots: - bluebird - supports-color + markdown-it-task-lists@2.1.1: {} + markdown-it@14.1.0: dependencies: argparse: 2.0.1 @@ -9321,6 +9352,14 @@ snapshots: dependencies: '@popperjs/core': 2.11.8 + tiptap-markdown@0.8.10(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)): + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@types/markdown-it': 13.0.9 + markdown-it: 14.1.0 + markdown-it-task-lists: 2.1.1 + prosemirror-markdown: 1.13.2 + tldts-core@6.1.86: {} tldts@6.1.86: diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 55d7d85..bab2147 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -3,6 +3,7 @@ import "@/styles/globals.css"; import { FileStoreInitializer } from "@/components/file-store-initializer"; import type { Metadata } from "next"; import { Geist } from "next/font/google"; +import { Toaster } from "sonner"; export const metadata: Metadata = { title: "Floki", @@ -29,6 +30,7 @@ export default function RootLayout({ {children} + ); diff --git a/src/components/editor/file-monaco-editor/hooks/use-monaco.ts b/src/components/editor/file-monaco-editor/hooks/use-monaco.ts index 45b7609..0e2e861 100644 --- a/src/components/editor/file-monaco-editor/hooks/use-monaco.ts +++ b/src/components/editor/file-monaco-editor/hooks/use-monaco.ts @@ -61,6 +61,7 @@ export const useMonaco = ({ "toml", "yaml", "yml", + "prisma" ] as const satisfies Parameters[0]["langs"]; for (const lang of ADDITIONAL_LANGUAGES) { diff --git a/src/components/tiptap-editor.tsx b/src/components/tiptap-editor.tsx index 7df9a13..d359148 100644 --- a/src/components/tiptap-editor.tsx +++ b/src/components/tiptap-editor.tsx @@ -1,11 +1,8 @@ "use client"; - -import Document from "@tiptap/extension-document"; -import Paragraph from "@tiptap/extension-paragraph"; -import Text from "@tiptap/extension-text"; import { EditorContent, useEditor } from "@tiptap/react"; import StarterKit from "@tiptap/starter-kit"; -import { marked } from "marked"; +import { Markdown } from "tiptap-markdown"; + interface TiptapViewerProps { content: string; className?: string; @@ -16,7 +13,7 @@ const TiptapViewer = ({ content, className }: TiptapViewerProps) => { extensions: [ StarterKit.configure({ heading: { - levels: [1, 2, 3], + levels: [1, 2, 3, 4, 5, 6], }, paragraph: { HTMLAttributes: { @@ -24,12 +21,14 @@ const TiptapViewer = ({ content, className }: TiptapViewerProps) => { }, }, }), - Document, - Paragraph, - Text, + Markdown.configure({ + html: true, + transformPastedText: true, + transformCopiedText: true, + breaks: true, + }), ], - content: marked.parse(content), - + content: content, editable: false, editorProps: { attributes: { From 15c54ad2bca15d3594a47a375faa5569ace2edf0 Mon Sep 17 00:00:00 2001 From: hamedmp Date: Tue, 29 Apr 2025 17:54:19 +0200 Subject: [PATCH 3/7] init of commandbar cmd-j --- public/workers/workers/file-system.worker.js | 6 +- src/app/(ide)/layout.tsx | 2 + src/components/commandbar/commandbar.tsx | 87 +++++++++ src/components/ui/dialog.tsx | 186 +++++++++---------- src/components/ui/textarea.tsx | 18 ++ src/workers/file-system.worker.ts | 6 +- 6 files changed, 206 insertions(+), 99 deletions(-) create mode 100644 src/components/commandbar/commandbar.tsx create mode 100644 src/components/ui/textarea.tsx diff --git a/public/workers/workers/file-system.worker.js b/public/workers/workers/file-system.worker.js index 219b73f..899b76b 100644 --- a/public/workers/workers/file-system.worker.js +++ b/public/workers/workers/file-system.worker.js @@ -79,15 +79,15 @@ async function listFilesNonRecursively(dirPath = "") { const dirHandle = await getDirHandleFromPath(dirPath); const files = []; try { - console.log("[Worker] Starting to list directory contents"); + // console.log("[Worker] Starting to list directory contents"); const handle = dirHandle; for await (const entry of handle.values()) { const entryPath = dirPath ? `${dirPath}/${entry.name}` : entry.name; - console.log("[Worker] Processing entry:", entryPath); + // console.log("[Worker] Processing entry:", entryPath); const fileInfo = await getFileInfo(entry, entry.name, entryPath); files.push(fileInfo); } - console.log("[Worker] Found", files.length, "files in directory"); + // console.log("[Worker] Found", files.length, "files in directory"); } catch (error) { console.error("[Worker] Error listing files in", dirPath, ":", error); diff --git a/src/app/(ide)/layout.tsx b/src/app/(ide)/layout.tsx index cd2da8e..5b6a671 100644 --- a/src/app/(ide)/layout.tsx +++ b/src/app/(ide)/layout.tsx @@ -1,3 +1,4 @@ +import { CommandDialogDemo } from "@/components/commandbar/commandbar"; import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"; import { cookies } from "next/headers"; import type { ReactNode } from "react"; @@ -14,6 +15,7 @@ export default async function IDELayout({ children }: { children: ReactNode }) { {/* */}
+ ); } diff --git a/src/components/commandbar/commandbar.tsx b/src/components/commandbar/commandbar.tsx new file mode 100644 index 0000000..da85633 --- /dev/null +++ b/src/components/commandbar/commandbar.tsx @@ -0,0 +1,87 @@ +"use client"; + +import { + Calculator, + Calendar, + CreditCard, + Settings, + Smile, + User, +} from "lucide-react"; +import * as React from "react"; + +import { + CommandDialog, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, + CommandShortcut, +} from "@/components/ui/command"; + +export function CommandDialogDemo() { + const [open, setOpen] = React.useState(false); + + React.useEffect(() => { + const down = (e: KeyboardEvent) => { + if (e.key === "j" && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + setOpen((open) => !open); + } + }; + + document.addEventListener("keydown", down); + return () => document.removeEventListener("keydown", down); + }, []); + + return ( + <> + {/*

+ Press{" "} + + J + +

*/} + + + + No results found. + + + + Calendar + + + + Search Emoji + + + + Calculator + + + + + + + Profile + ⌘P + + + + Billing + ⌘B + + + + Settings + ⌘S + + + + + + ); +} diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx index 776e94a..7d7a9d3 100644 --- a/src/components/ui/dialog.tsx +++ b/src/components/ui/dialog.tsx @@ -1,135 +1,135 @@ -"use client"; +"use client" -import * as DialogPrimitive from "@radix-ui/react-dialog"; -import { XIcon } from "lucide-react"; -import type * as React from "react"; +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { XIcon } from "lucide-react" -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils" function Dialog({ - ...props + ...props }: React.ComponentProps) { - return ; + return } function DialogTrigger({ - ...props + ...props }: React.ComponentProps) { - return ; + return } function DialogPortal({ - ...props + ...props }: React.ComponentProps) { - return ; + return } function DialogClose({ - ...props + ...props }: React.ComponentProps) { - return ; + return } function DialogOverlay({ - className, - ...props + className, + ...props }: React.ComponentProps) { - return ( - - ); + return ( + + ) } function DialogContent({ - className, - children, - ...props + className, + children, + ...props }: React.ComponentProps) { - return ( - - - - {children} - - - Close - - - - ); + return ( + + + + {children} + + + Close + + + + ) } function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ); + return ( +
+ ) } function DialogFooter({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ); + return ( +
+ ) } function DialogTitle({ - className, - ...props + className, + ...props }: React.ComponentProps) { - return ( - - ); + return ( + + ) } function DialogDescription({ - className, - ...props + className, + ...props }: React.ComponentProps) { - return ( - - ); + return ( + + ) } export { - Dialog, - DialogClose, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogOverlay, - DialogPortal, - DialogTitle, - DialogTrigger, -}; + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +} diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx new file mode 100644 index 0000000..59c799a --- /dev/null +++ b/src/components/ui/textarea.tsx @@ -0,0 +1,18 @@ +import type * as React from "react"; + +import { cn } from "@/lib/utils"; + +function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { + return ( +