diff --git a/package-lock.json b/package-lock.json index 62bd751..cc8b89c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", + "@radix-ui/react-context-menu": "^2.1.5", "@remirror/pm": "^2.0.8", "@remirror/react": "^2.0.35", "@remirror/react-editors": "^1.0.40", @@ -18,9 +19,11 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.21.2", - "remirror": "^2.0.38" + "remirror": "^2.0.38", + "use-debounce": "^10.0.0" }, "devDependencies": { + "@tailwindcss/typography": "^0.5.10", "@types/react": "^18.2.43", "@types/react-dom": "^18.2.17", "@typescript-eslint/eslint-plugin": "^6.14.0", @@ -1658,6 +1661,534 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@radix-ui/primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz", + "integrity": "sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==", + "dependencies": { + "@babel/runtime": "^7.13.10" + } + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz", + "integrity": "sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "1.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.3.tgz", + "integrity": "sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz", + "integrity": "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.1.tgz", + "integrity": "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context-menu": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.1.5.tgz", + "integrity": "sha512-R5XaDj06Xul1KGb+WP8qiOh7tKJNz2durpLBXAGZjSVtctcRFCuEvy2gtMwRJGePwQQE5nV77gs4FwRi8T+r2g==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-menu": "2.0.6", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-controllable-state": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.1.tgz", + "integrity": "sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz", + "integrity": "sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-escape-keydown": "1.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz", + "integrity": "sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz", + "integrity": "sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.1.tgz", + "integrity": "sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-layout-effect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.0.6.tgz", + "integrity": "sha512-BVkFLS+bUC8HcImkRKPSiVumA1VPOOEC5WBMiT+QAVsPzW1FJzI9KnqgGxVDPBcql5xXrHkD3JOVoXWEXD8SYA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-collection": "1.0.3", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-direction": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.5", + "@radix-ui/react-focus-guards": "1.0.1", + "@radix-ui/react-focus-scope": "1.0.4", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-popper": "1.1.3", + "@radix-ui/react-portal": "1.0.4", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-roving-focus": "1.0.4", + "@radix-ui/react-slot": "1.0.2", + "@radix-ui/react-use-callback-ref": "1.0.1", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.3.tgz", + "integrity": "sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.0.3", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-layout-effect": "1.0.1", + "@radix-ui/react-use-rect": "1.0.1", + "@radix-ui/react-use-size": "1.0.1", + "@radix-ui/rect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.4.tgz", + "integrity": "sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "1.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.1.tgz", + "integrity": "sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-use-layout-effect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz", + "integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-slot": "1.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz", + "integrity": "sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-collection": "1.0.3", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-direction": "1.0.1", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-controllable-state": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", + "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz", + "integrity": "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz", + "integrity": "sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-callback-ref": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz", + "integrity": "sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-callback-ref": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz", + "integrity": "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.1.tgz", + "integrity": "sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/rect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.0.1.tgz", + "integrity": "sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-layout-effect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.1.tgz", + "integrity": "sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==", + "dependencies": { + "@babel/runtime": "^7.13.10" + } + }, "node_modules/@remirror/core": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/@remirror/core/-/core-2.0.19.tgz", @@ -3327,6 +3858,34 @@ "@svgmoji/core": "^3.2.0" } }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.10.tgz", + "integrity": "sha512-Pe8BuPJQJd3FfRnm6H0ulKIGoMEQS+Vq01R6M5aCrFB/ccR/shT+0kXLjouGC1gFLm9hopTFN+DMP0pfwRWzPw==", + "dev": true, + "dependencies": { + "lodash.castarray": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, + "node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -4383,6 +4942,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -5173,6 +5737,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", @@ -5488,6 +6060,14 @@ "node": ">= 0.4" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/is-alphabetical": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", @@ -6014,6 +6594,18 @@ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, + "node_modules/lodash.castarray": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", + "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==", + "dev": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -7049,6 +7641,51 @@ "node": ">=0.10.0" } }, + "node_modules/react-remove-scroll": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", + "integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.3", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz", + "integrity": "sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==", + "dependencies": { + "react-style-singleton": "^2.2.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-router": { "version": "6.21.2", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.2.tgz", @@ -7079,6 +7716,28 @@ "react-dom": ">=16.8" } }, + "node_modules/react-style-singleton": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", + "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", + "dependencies": { + "get-nonce": "^1.0.0", + "invariant": "^2.2.4", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -8117,6 +8776,37 @@ "punycode": "^2.1.0" } }, + "node_modules/use-callback-ref": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.1.tgz", + "integrity": "sha512-Lg4Vx1XZQauB42Hw3kK7JM6yjVjgFmFC5/Ab797s79aARomD2nEErc4mCgM8EZrARLmmbWpi5DGCadmK50DcAQ==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-debounce": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-10.0.0.tgz", + "integrity": "sha512-XRjvlvCB46bah9IBXVnq/ACP2lxqXyZj0D9hj4K5OzNroMDpTEBg8Anuh1/UfRTRs7pLhQ+RiNxxwZu9+MVl1A==", + "engines": { + "node": ">= 16.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/use-isomorphic-layout-effect": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", @@ -8141,6 +8831,27 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/use-sidecar": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", + "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index b610b80..0cc512f 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite", + "dev": "vite --host", "build": "tsc && vite build", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview" @@ -12,6 +12,7 @@ "dependencies": { "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", + "@radix-ui/react-context-menu": "^2.1.5", "@remirror/pm": "^2.0.8", "@remirror/react": "^2.0.35", "@remirror/react-editors": "^1.0.40", @@ -20,9 +21,11 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.21.2", - "remirror": "^2.0.38" + "remirror": "^2.0.38", + "use-debounce": "^10.0.0" }, "devDependencies": { + "@tailwindcss/typography": "^0.5.10", "@types/react": "^18.2.43", "@types/react-dom": "^18.2.17", "@typescript-eslint/eslint-plugin": "^6.14.0", diff --git a/sample-markdown-folder copy/.obsidian/app.json b/sample-markdown-folder copy/.obsidian/app.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/sample-markdown-folder copy/.obsidian/app.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/sample-markdown-folder copy/.obsidian/appearance.json b/sample-markdown-folder copy/.obsidian/appearance.json new file mode 100644 index 0000000..c8c365d --- /dev/null +++ b/sample-markdown-folder copy/.obsidian/appearance.json @@ -0,0 +1,3 @@ +{ + "accentColor": "" +} \ No newline at end of file diff --git a/sample-markdown-folder copy/.obsidian/core-plugins-migration.json b/sample-markdown-folder copy/.obsidian/core-plugins-migration.json new file mode 100644 index 0000000..436f43c --- /dev/null +++ b/sample-markdown-folder copy/.obsidian/core-plugins-migration.json @@ -0,0 +1,30 @@ +{ + "file-explorer": true, + "global-search": true, + "switcher": true, + "graph": true, + "backlink": true, + "canvas": true, + "outgoing-link": true, + "tag-pane": true, + "properties": false, + "page-preview": true, + "daily-notes": true, + "templates": true, + "note-composer": true, + "command-palette": true, + "slash-command": false, + "editor-status": true, + "bookmarks": true, + "markdown-importer": false, + "zk-prefixer": false, + "random-note": false, + "outline": true, + "word-count": true, + "slides": false, + "audio-recorder": false, + "workspaces": false, + "file-recovery": true, + "publish": false, + "sync": false +} \ No newline at end of file diff --git a/sample-markdown-folder copy/.obsidian/core-plugins.json b/sample-markdown-folder copy/.obsidian/core-plugins.json new file mode 100644 index 0000000..9405bfd --- /dev/null +++ b/sample-markdown-folder copy/.obsidian/core-plugins.json @@ -0,0 +1,20 @@ +[ + "file-explorer", + "global-search", + "switcher", + "graph", + "backlink", + "canvas", + "outgoing-link", + "tag-pane", + "page-preview", + "daily-notes", + "templates", + "note-composer", + "command-palette", + "editor-status", + "bookmarks", + "outline", + "word-count", + "file-recovery" +] \ No newline at end of file diff --git a/sample-markdown-folder copy/.obsidian/graph.json b/sample-markdown-folder copy/.obsidian/graph.json new file mode 100644 index 0000000..e21a18d --- /dev/null +++ b/sample-markdown-folder copy/.obsidian/graph.json @@ -0,0 +1,22 @@ +{ + "collapse-filter": true, + "search": "", + "showTags": false, + "showAttachments": false, + "hideUnresolved": false, + "showOrphans": true, + "collapse-color-groups": true, + "colorGroups": [], + "collapse-display": true, + "showArrow": false, + "textFadeMultiplier": 0, + "nodeSizeMultiplier": 1, + "lineSizeMultiplier": 1, + "collapse-forces": true, + "centerStrength": 0.518713248970312, + "repelStrength": 10, + "linkStrength": 1, + "linkDistance": 250, + "scale": 1, + "close": false +} \ No newline at end of file diff --git a/sample-markdown-folder copy/.obsidian/workspace.json b/sample-markdown-folder copy/.obsidian/workspace.json new file mode 100644 index 0000000..5fd1936 --- /dev/null +++ b/sample-markdown-folder copy/.obsidian/workspace.json @@ -0,0 +1,161 @@ +{ + "main": { + "id": "8d4e0d7b1d235c77", + "type": "split", + "children": [ + { + "id": "9e21d6ab2d48227d", + "type": "tabs", + "children": [ + { + "id": "aa18295d66dc0f82", + "type": "leaf", + "state": { + "type": "markdown", + "state": { + "file": "folder/subfolder/test66.md", + "mode": "source", + "source": false + } + } + } + ] + } + ], + "direction": "vertical" + }, + "left": { + "id": "998e02dba9179cfc", + "type": "split", + "children": [ + { + "id": "4e6b8086a6104914", + "type": "tabs", + "children": [ + { + "id": "ca0bafc2d5792509", + "type": "leaf", + "state": { + "type": "file-explorer", + "state": { + "sortOrder": "alphabetical" + } + } + }, + { + "id": "e1705145d52813ef", + "type": "leaf", + "state": { + "type": "search", + "state": { + "query": "", + "matchingCase": false, + "explainSearch": false, + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical" + } + } + }, + { + "id": "a92a326292eb8a13", + "type": "leaf", + "state": { + "type": "bookmarks", + "state": {} + } + } + ] + } + ], + "direction": "horizontal", + "width": 300 + }, + "right": { + "id": "5b0aafda22173b8b", + "type": "split", + "children": [ + { + "id": "415a8742e586990c", + "type": "tabs", + "children": [ + { + "id": "ecaa4a612c4324f0", + "type": "leaf", + "state": { + "type": "backlink", + "state": { + "file": "folder/subfolder/test66.md", + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical", + "showSearch": false, + "searchQuery": "", + "backlinkCollapsed": false, + "unlinkedCollapsed": true + } + } + }, + { + "id": "24d734be5e8f0959", + "type": "leaf", + "state": { + "type": "outgoing-link", + "state": { + "file": "folder/subfolder/test66.md", + "linksCollapsed": false, + "unlinkedCollapsed": true + } + } + }, + { + "id": "44acc73aabc3df82", + "type": "leaf", + "state": { + "type": "tag", + "state": { + "sortOrder": "frequency", + "useHierarchy": true + } + } + }, + { + "id": "66ac4ddd7b6a46bd", + "type": "leaf", + "state": { + "type": "outline", + "state": { + "file": "folder/subfolder/test66.md" + } + } + } + ] + } + ], + "direction": "horizontal", + "width": 300, + "collapsed": true + }, + "left-ribbon": { + "hiddenItems": { + "switcher:Open quick switcher": false, + "graph:Open graph view": false, + "canvas:Create new canvas": false, + "daily-notes:Open today's daily note": false, + "templates:Insert template": false, + "command-palette:Open command palette": false + } + }, + "active": "aa18295d66dc0f82", + "lastOpenFiles": [ + "folder/subfolder/test55.md", + "folder/subfolder/test66.md", + "This is a test o k...sdffdsafd .md", + "Todo list ✅.md", + "Untitled.canvas", + "Untitled 1.canvas", + "1200x627 - Glowing.png", + "2024-01-21.md", + "folder/hmm .. pretty cool✅.md" + ] +} \ No newline at end of file diff --git a/sample-markdown-folder copy/1200x627 - Glowing.png b/sample-markdown-folder copy/1200x627 - Glowing.png new file mode 100644 index 0000000..92bdbb0 Binary files /dev/null and b/sample-markdown-folder copy/1200x627 - Glowing.png differ diff --git a/sample-markdown-folder copy/2024-01-21.md b/sample-markdown-folder copy/2024-01-21.md new file mode 100644 index 0000000..e69de29 diff --git a/sample-markdown-folder copy/This is a test o k...sdffdsafd .md b/sample-markdown-folder copy/This is a test o k...sdffdsafd .md new file mode 100644 index 0000000..1a8dad0 --- /dev/null +++ b/sample-markdown-folder copy/This is a test o k...sdffdsafd .md @@ -0,0 +1,3 @@ +this is a tesr this is a test this is a test sdfasdsadfads asdfsadf + +thisi this is a test erterstrete dasf sdgfasdf this is a test sdfgsdfg sdafdfs asdfasdf \ No newline at end of file diff --git "a/sample-markdown-folder copy/Todo list \342\234\205.md" "b/sample-markdown-folder copy/Todo list \342\234\205.md" new file mode 100644 index 0000000..6580546 --- /dev/null +++ "b/sample-markdown-folder copy/Todo list \342\234\205.md" @@ -0,0 +1,10 @@ +* add support for images + interesteing + +* add support for links + +* add menu bar + +* fix styling for bullet points, etc. + +Ok so it's pretty basic, cool got it sdfasdf!![[1200x627 - Glowing.png]] \ No newline at end of file diff --git a/sample-markdown-folder copy/Untitled 1.canvas b/sample-markdown-folder copy/Untitled 1.canvas new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/sample-markdown-folder copy/Untitled 1.canvas @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/sample-markdown-folder copy/Untitled.canvas b/sample-markdown-folder copy/Untitled.canvas new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/sample-markdown-folder copy/Untitled.canvas @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git "a/sample-markdown-folder copy/folder/hmm .. pretty cool\342\234\205.md" "b/sample-markdown-folder copy/folder/hmm .. pretty cool\342\234\205.md" new file mode 100644 index 0000000..ee122c8 --- /dev/null +++ "b/sample-markdown-folder copy/folder/hmm .. pretty cool\342\234\205.md" @@ -0,0 +1,17 @@ +hello this is a **test** + +# This is a testasdfasdf + +hello this is a **test** + +# This is a testasdfasd sdafasdfasdfsad asdfasdfa sdfasdf asdf aasdf asdf asd asdf asdf asd asdf asdf asd asdf asdf asd asdf asdf asd asdf asdf asd sdf asd fsadfasdsad ffsddsfsad fasdfasd fassdfa sad fasd fsasdasdfasdfsadfasdfasdfsadfasdfasdff asdfsadfasdfsdfasdf + +hello this is a **test** + +# This is a testasdfasdf asdfsadfasdfsdfasdf + +hello this is a **test** + +# This is a testasdfasdf asdfsadfasdfsdfasdf + +# asdfsadfasdfsdfasdf \ No newline at end of file diff --git a/sample-markdown-folder/folder/subfolder/test5.md b/sample-markdown-folder copy/folder/subfolder/test55.md similarity index 56% rename from sample-markdown-folder/folder/subfolder/test5.md rename to sample-markdown-folder copy/folder/subfolder/test55.md index 0b60c41..372006e 100644 --- a/sample-markdown-folder/folder/subfolder/test5.md +++ b/sample-markdown-folder copy/folder/subfolder/test55.md @@ -1,3 +1,3 @@ hello this is a test -# test 5test \ No newline at end of file +# test 4 asdfsadf \ No newline at end of file diff --git a/sample-markdown-folder copy/folder/subfolder/test66.md b/sample-markdown-folder copy/folder/subfolder/test66.md new file mode 100644 index 0000000..c2bd329 --- /dev/null +++ b/sample-markdown-folder copy/folder/subfolder/test66.md @@ -0,0 +1,6 @@ +this is a test +[link](./test55) + +[test] + +[[test55]] diff --git "a/sample-markdown-folder copy/folder/subfolder/thi si a test TESt \342\234\205" "b/sample-markdown-folder copy/folder/subfolder/thi si a test TESt \342\234\205" new file mode 100644 index 0000000..e69de29 diff --git "a/sample-markdown-folder/Todo list \342\234\205.md" "b/sample-markdown-folder/Todo list \342\234\205.md" new file mode 100644 index 0000000..5427467 --- /dev/null +++ "b/sample-markdown-folder/Todo list \342\234\205.md" @@ -0,0 +1,49 @@ +* ✅ add support for images + +* ✅ added ability for images reading + + * ✅ added ability to insert images + +* ✅ add support for delete files/folders + +* ✅ add support for new files/folders + +* ✅ add support for links + + * ❌ add support to add links + +* add menu bar + +* ✅show only png, webp, jpeg, jpg, svg, md files + + * prevent the upload of images that aren't in this list + + * make it save when adding an image + + * make it support adding (1) after an image if needed + +* ✅ fix styling for bullet points, etc. + +* add directory/project picker + + * improve permissions / add ability for indexdb? + + * add intro screen to select 'directory' with option to start new, or select an existing list of directories + +* better support conflict in names + +* support folder renames + +* ✅ better handle folder open/tree structure after create file + +* add refresh for files, either button or automatic + +* have a create modal with the create and open existing + +* ✅ when changing to page, it should not auto-focus + +* ✅ add scroll to filetree + +* ❌ styling for mobile + + * local file system doesn't even work on mobile... \ No newline at end of file diff --git a/sample-markdown-folder/folder/Untitled Folder (1)/Maybe I need to debounce more.md b/sample-markdown-folder/folder/Untitled Folder (1)/Maybe I need to debounce more.md new file mode 100644 index 0000000..e69de29 diff --git a/sample-markdown-folder/folder/Untitled Folder (1)/This is a test.md b/sample-markdown-folder/folder/Untitled Folder (1)/This is a test.md new file mode 100644 index 0000000..b3da463 --- /dev/null +++ b/sample-markdown-folder/folder/Untitled Folder (1)/This is a test.md @@ -0,0 +1,18 @@ +Interesting, it's not that bad + +## He's a prick + +### How long since you spoke + +**Ok pretty cool** + +* Where are my + + +1. Where are my numbers + +2. Test + + +Tes +test \ No newline at end of file diff --git a/sample-markdown-folder/folder/Untitled Folder (1)/Untitled Folder/Happy thanksgiving!.md b/sample-markdown-folder/folder/Untitled Folder (1)/Untitled Folder/Happy thanksgiving!.md new file mode 100644 index 0000000..3596890 --- /dev/null +++ b/sample-markdown-folder/folder/Untitled Folder (1)/Untitled Folder/Happy thanksgiving!.md @@ -0,0 +1 @@ +**Hey** _there_! Happy thanksgiving! :D \ No newline at end of file diff --git a/sample-markdown-folder/folder/Untitled Folder (1)/Untitled Folder/Untitled Folder/Test.md b/sample-markdown-folder/folder/Untitled Folder (1)/Untitled Folder/Untitled Folder/Test.md new file mode 100644 index 0000000..4b7f922 --- /dev/null +++ b/sample-markdown-folder/folder/Untitled Folder (1)/Untitled Folder/Untitled Folder/Test.md @@ -0,0 +1 @@ +This is a test, who can guess how it works \ No newline at end of file diff --git a/sample-markdown-folder/folder/Untitled Folder (1)/Untitled Folder/Untitled Folder/We'll see you at Christmas!.md b/sample-markdown-folder/folder/Untitled Folder (1)/Untitled Folder/Untitled Folder/We'll see you at Christmas!.md new file mode 100644 index 0000000..4cbaf6f --- /dev/null +++ b/sample-markdown-folder/folder/Untitled Folder (1)/Untitled Folder/Untitled Folder/We'll see you at Christmas!.md @@ -0,0 +1 @@ +❤️ diff --git a/sample-markdown-folder/folder/Untitled Folder (1)/Untitled Folder/Untitled.md b/sample-markdown-folder/folder/Untitled Folder (1)/Untitled Folder/Untitled.md new file mode 100644 index 0000000..3d5316f --- /dev/null +++ b/sample-markdown-folder/folder/Untitled Folder (1)/Untitled Folder/Untitled.md @@ -0,0 +1,17 @@ +this is a [link](./Happy%20thanksgiving!.md) re + +this is a [test](./Happy%20thanksgiving!.md) + +\[test\](./Happy%20thanksgiving!) + +this is **_another_** [test](../Untitled%20Folder/Happy%20thanksgiving!.md) + +[google.com](google.com) + +test ./test.md test.md\\ + +[google.co](https:////google.co). google.md + +remirror doesn't link to relative files... what to do what to do + +goog.com this is a test google.com \ No newline at end of file diff --git a/sample-markdown-folder/folder/Untitled.md b/sample-markdown-folder/folder/Untitled.md new file mode 100644 index 0000000..2a8cd99 --- /dev/null +++ b/sample-markdown-folder/folder/Untitled.md @@ -0,0 +1,5 @@ +This is a test + +Where is the only disruption + +Why can't I see \ No newline at end of file diff --git a/sample-markdown-folder/folder/subfolder/4 differences!.png b/sample-markdown-folder/folder/subfolder/4 differences!.png new file mode 100644 index 0000000..096a23f Binary files /dev/null and b/sample-markdown-folder/folder/subfolder/4 differences!.png differ diff --git a/sample-markdown-folder/folder/subfolder/advocacy-2023-b.png b/sample-markdown-folder/folder/subfolder/advocacy-2023-b.png new file mode 100644 index 0000000..cddd17c Binary files /dev/null and b/sample-markdown-folder/folder/subfolder/advocacy-2023-b.png differ diff --git a/sample-markdown-folder/folder/subfolder/alex rider books.jpeg b/sample-markdown-folder/folder/subfolder/alex rider books.jpeg new file mode 100644 index 0000000..d746d18 Binary files /dev/null and b/sample-markdown-folder/folder/subfolder/alex rider books.jpeg differ diff --git a/sample-markdown-folder/folder/subfolder/angular.png b/sample-markdown-folder/folder/subfolder/angular.png new file mode 100644 index 0000000..45f6380 Binary files /dev/null and b/sample-markdown-folder/folder/subfolder/angular.png differ diff --git a/sample-markdown-folder/folder/subfolder/astro.png b/sample-markdown-folder/folder/subfolder/astro.png new file mode 100644 index 0000000..38e0125 Binary files /dev/null and b/sample-markdown-folder/folder/subfolder/astro.png differ diff --git a/sample-markdown-folder/folder/subfolder/azure.png b/sample-markdown-folder/folder/subfolder/azure.png new file mode 100644 index 0000000..7c75031 Binary files /dev/null and b/sample-markdown-folder/folder/subfolder/azure.png differ diff --git a/sample-markdown-folder/folder/subfolder/remix.png b/sample-markdown-folder/folder/subfolder/remix.png new file mode 100644 index 0000000..92bdbb0 Binary files /dev/null and b/sample-markdown-folder/folder/subfolder/remix.png differ diff --git a/sample-markdown-folder/folder/subfolder/test4.md b/sample-markdown-folder/folder/subfolder/test55.md similarity index 70% rename from sample-markdown-folder/folder/subfolder/test4.md rename to sample-markdown-folder/folder/subfolder/test55.md index 6203a5f..9489fdd 100644 --- a/sample-markdown-folder/folder/subfolder/test4.md +++ b/sample-markdown-folder/folder/subfolder/test55.md @@ -1,3 +1,3 @@ hello this is a test -# test 4 +# test 4 \ No newline at end of file diff --git "a/sample-markdown-folder/folder/subfolder/thi si a test TESt \342\234\205 \360\237\247\220.md" "b/sample-markdown-folder/folder/subfolder/thi si a test TESt \342\234\205 \360\237\247\220.md" new file mode 100644 index 0000000..0f18c71 --- /dev/null +++ "b/sample-markdown-folder/folder/subfolder/thi si a test TESt \342\234\205 \360\237\247\220.md" @@ -0,0 +1,7 @@ +this is a link + +[test55.md](./test55.md) + +![Alt text](./remix.png) + +![Alt text](./alex.jpeg) diff --git a/sample-markdown-folder/folder/test3.md b/sample-markdown-folder/folder/test3.md deleted file mode 100644 index 37d9102..0000000 --- a/sample-markdown-folder/folder/test3.md +++ /dev/null @@ -1,3 +0,0 @@ -hello this is a **test** - -# This is a test \ No newline at end of file diff --git a/sample-markdown-folder/folder/wait renaming works!.md b/sample-markdown-folder/folder/wait renaming works!.md new file mode 100644 index 0000000..dc4286f --- /dev/null +++ b/sample-markdown-folder/folder/wait renaming works!.md @@ -0,0 +1,6 @@ +asdfdf + +1. so numbering works. + + +* but bullet points doesn't work \ No newline at end of file diff --git a/sample-markdown-folder/test.md b/sample-markdown-folder/test.md deleted file mode 100644 index 84daa74..0000000 --- a/sample-markdown-folder/test.md +++ /dev/null @@ -1,3 +0,0 @@ -hello this is a test - -# test twerk \ No newline at end of file diff --git a/sample-markdown-folder/test2.md b/sample-markdown-folder/test2.md deleted file mode 100644 index b18c361..0000000 --- a/sample-markdown-folder/test2.md +++ /dev/null @@ -1,11 +0,0 @@ -hello this is a test - -# test 2asdfsadoohtest - -this is a new test - -**intersteingasdfasd** - -## this is another test asdf - -sadfasdojasljfsad \ No newline at end of file diff --git a/src/components/FileSystemAdapters/FileSystem/Folder.tsx b/src/components/FileSystemAdapters/FileSystem/Folder.tsx index 6dd4a48..87e33bd 100644 --- a/src/components/FileSystemAdapters/FileSystem/Folder.tsx +++ b/src/components/FileSystemAdapters/FileSystem/Folder.tsx @@ -1,34 +1,70 @@ import { useState } from "react"; import DirectoryNode from "../../../models/DirectoryNode"; -import { ChevronDown, ChevronRight, ChevronUp } from "lucide-react"; +import { ChevronDown, ChevronRight, FilePlus, FolderPlus } from "lucide-react"; +import RightClickMenu from "./RightClickMenu"; +import * as ContextMenu from "@radix-ui/react-context-menu"; export function Folder({ node, depth, handleFileSelect, + currentlySelectedFile, + handleDeleteFile, + handleCreateFile, + handleCreateFolder, }: { - node: DirectoryNode | null; + node: DirectoryNode; depth: number; handleFileSelect: (file: DirectoryNode) => void; + currentlySelectedFile: DirectoryNode | null; + handleDeleteFile: (node: DirectoryNode) => void; + handleCreateFile: (node: DirectoryNode) => void; + handleCreateFolder: (node: DirectoryNode) => void; }) { const [expanded, setExpanded] = useState(depth === 0); - if (!node) return null; return ( -
+
{depth === 0 ? null : (
{ setExpanded(!expanded); }} > - {expanded ? ( - - ) : ( - - )} -
{node.name}
+
+ {expanded ? ( + + ) : ( + + )} +
+ +
+
{node.name}
+
+
+ { + handleCreateFile(node); + e.stopPropagation(); + }} + /> +
+
+ { + handleCreateFolder(node); + e.stopPropagation(); + }} + /> +
+
+
)}
{node.children.map((child) => ( -
- {child.children.length === 0 ? ( // Render file -
{ - handleFileSelect(child); - }} - > -
- {child.name} - {child.unsavedChanges ? "*" : ""} -
-
- ) : ( - // Recursively render directory - )} +
+ + + {!child.isDirectory() ? ( // Render file or folder depending on amount of children +
handleFileSelect(child)} + > +
+ {child.getName()} + {child.unsavedChanges ? "*" : ""} +
+
+ ) : ( + // Recursively render directory + )} +
+ handleDeleteFile(child)} /> +
))}
diff --git a/src/components/FileSystemAdapters/FileSystem/LocalFileSystem.tsx b/src/components/FileSystemAdapters/FileSystem/LocalFileSystem.tsx index cca4ccf..c7166c3 100644 --- a/src/components/FileSystemAdapters/FileSystem/LocalFileSystem.tsx +++ b/src/components/FileSystemAdapters/FileSystem/LocalFileSystem.tsx @@ -1,19 +1,27 @@ +// The main function of this file is to define the operations for the local file system as functions +// and pass them down to the Folder component. The Folder component is then responsible for rendering +// and calling them + import { useState, useEffect } from "react"; -import DirectoryNode from "../../../models/DirectoryNode"; +import DirectoryNode, { + createDirectoryNode, +} from "../../../models/DirectoryNode"; import { set } from "remirror"; import { Folder } from "./Folder"; //sample react function export const LocalFileSystem = ({ + selectedDirectory, + setSelectedDirectory, selectedFile, setSelectedFile, }: { + selectedDirectory: DirectoryNode | null; + setSelectedDirectory: (directory: DirectoryNode | null) => void; selectedFile: DirectoryNode | null; - setSelectedFile: (file: DirectoryNode | null) => void; + setSelectedFile: (file: DirectoryNode | undefined) => void; + selectedFileName: string | null; }) => { - const [selectedDirectory, setSelectedDirectory] = - useState(null); - useEffect(() => { const fetchEntries = async () => { if (selectedDirectory?.directoryHandle) { @@ -46,70 +54,112 @@ export const LocalFileSystem = ({ return rootDirectoryNode; }; - const createDirectoryNode = async ( - directoryHandle: FileSystemDirectoryHandle, - parent?: DirectoryNode - ): Promise => { - const entries: DirectoryNode[] = []; - - let directoryNode = new DirectoryNode( - directoryHandle.name, - false, - entries, - undefined, - directoryHandle, - parent - ); - - //@ts-ignore - for await (const entry of directoryHandle.values()) { - if (entry.kind === "directory") { - const subdirectoryHandle = entry as FileSystemDirectoryHandle; - const subdirectoryNode = await createDirectoryNode( - subdirectoryHandle, - directoryNode - ); - entries.push(subdirectoryNode); - } else { - const fileHandle = entry as FileSystemFileHandle; - const fileNode = new DirectoryNode( - entry.name, - false, - [], - fileHandle, - undefined, - directoryNode + const handleFileSelect = async (node: DirectoryNode) => { + setSelectedFile(node); + }; + + const handleDeleteFile = async (node: DirectoryNode) => { + console.log("deleting file"); + const parent = await node.delete(); + setSelectedFile(getClosestParentsFirstFile(parent)); // TODO: set to root directory + }; + + const createFileHandlingDuplicates = async (parent: DirectoryNode) => { + console.log("creating file"); + let counter = 0; + while (true && counter < 100) { + //limit to 100 + try { + const node = await parent.createFile( + counter === 0 ? "Untitled.md" : `Untitled (${counter}).md` ); - console.log( - "created file node, full path should be:", - fileNode.getFullPath() + setSelectedFile(node); + return; + } catch (error) { + counter++; + } + } + + throw new Error("Could not create file"); + }; + + const handleCreateFile = async (parent: DirectoryNode) => { + console.log("creating file"); + + try { + createFileHandlingDuplicates(parent); + } catch (error) { + console.error("Error creating file:", error); + alert( + "Error creating file, too many conflicts when trying to create the file." + ); + } + }; + + const createFolderHandlingDuplicates = async (parent: DirectoryNode) => { + let counter = 0; + while (true && counter < 100) { + //limit to 100 + try { + const node = await parent.createFolder( + counter === 0 ? "Untitled Folder" : `Untitled Folder (${counter})` ); - entries.push(fileNode); + setSelectedFile(node); + return; + } catch (error) { + counter++; } } - directoryNode.children = entries; + throw new Error("Could not create folder"); + }; - return directoryNode; + const handleCreateFolder = async (parent: DirectoryNode) => { + try { + createFolderHandlingDuplicates(parent); + } catch (error) { + console.error("Error creating folder:", error); + alert( + "Error creating folder, too many conflicts when trying to create the folder." + ); + } }; - const handleFileSelect = async (node: DirectoryNode) => { - await node.loadFileContent(); - setSelectedFile(node); + const getClosestParentsFirstFile = ( + node: DirectoryNode | undefined + ): DirectoryNode | undefined => { + if (!node) return node; + if (node.children.length > 0) { + for (const child of node.children) { + if (!child.isDirectory()) { + return child; + } + } + } + + if (node.parent) { + return getClosestParentsFirstFile(node.parent); + } else { + return node; + } }; return ( -
+
{selectedDirectory && (
- {selectedDirectory.name} + {selectedDirectory.getName()}
diff --git a/src/components/FileSystemAdapters/FileSystem/RightClickMenu.tsx b/src/components/FileSystemAdapters/FileSystem/RightClickMenu.tsx new file mode 100644 index 0000000..55f139e --- /dev/null +++ b/src/components/FileSystemAdapters/FileSystem/RightClickMenu.tsx @@ -0,0 +1,29 @@ +import { FilePlus, Trash } from "lucide-react"; +import * as ContextMenu from "@radix-ui/react-context-menu"; +import DirectoryNode from "../../../models/DirectoryNode"; + +const RightClickMenu = ({ onDelete }: { onDelete: () => void }) => { + return ( + + + { + onDelete(); + }} + > + +
Delete
+
+
+
+ ); +}; + +export default RightClickMenu; diff --git a/src/components/MarkdownEditor/FileEditor.tsx b/src/components/MarkdownEditor/FileEditor.tsx index 3da5135..2cb9cd2 100644 --- a/src/components/MarkdownEditor/FileEditor.tsx +++ b/src/components/MarkdownEditor/FileEditor.tsx @@ -3,13 +3,36 @@ import { RemirrorComponent } from "./RemirrorComponent"; export const FileEditor: React.FC = ({ selectedFile, + setSelectedFile, }: { selectedFile: DirectoryNode | null; + setSelectedFile: (file: DirectoryNode) => void; }) => { + const handleOnChangeFileName = async ( + e: React.FormEvent + ) => { + if (selectedFile) { + try { + await selectedFile.renameFile(e.currentTarget.value); + setSelectedFile(selectedFile.getCopy()); + } catch (error) { + console.error(error); + } + } + }; + return ( -
- - -
+ <> + + + ); }; diff --git a/src/components/MarkdownEditor/RemirrorComponent.tsx b/src/components/MarkdownEditor/RemirrorComponent.tsx index e34c4ad..648b00b 100644 --- a/src/components/MarkdownEditor/RemirrorComponent.tsx +++ b/src/components/MarkdownEditor/RemirrorComponent.tsx @@ -4,6 +4,9 @@ import { EditorComponent } from "@remirror/react"; import { Menu } from "./RemirrorMenu"; import { RemirrorMarkdownEditor } from "./RemirrorMarkdownEditor"; import DirectoryNode from "../../models/DirectoryNode"; +import { useDebouncedCallback } from "use-debounce"; +import { ImageExtensionAttributes } from "remirror/extensions"; +import { DelayedPromiseCreator } from "@remirror/core"; // Hooks can be added to the context without the need for creating custom components const hooks = [ @@ -24,41 +27,120 @@ const hooks = [ }, ]; +export type DelayedImage = DelayedPromiseCreator; + +export interface FileWithProgress { + file: File; + progress: SetProgress; +} + +interface SetProgress { + // Define the SetProgress interface if not already available + // You might need to adjust this based on your actual implementation + setProgress: (progress: number) => void; +} + export const RemirrorComponent: React.FC = ({ selectedFile, + setSelectedFile, }: { - selectedFile: DirectoryNode | null; + selectedFile: DirectoryNode; + setSelectedFile: (file: DirectoryNode) => void; }) => { - useEffect(() => { - console.log(`Markdown content: ${selectedFile?.fileContent}`); - }, [selectedFile?.fileContent]); - - if (!selectedFile?.fileContent) { - return
Empty file
; - } + const debouncedPersistMarkdown = useDebouncedCallback( + // function + (markdown) => persistMarkdown(markdown), + // delay in ms + 500 + ); const handleMarkdownChange = (markdown: string) => { + setSelectedFile(selectedFile?.updateFileContent(markdown).getCopy()); //updating state to cause rerender (direct updates to objects/objecttree do not cause rerender) + debouncedPersistMarkdown(markdown); + }; + + const persistMarkdown = async (markdown: string) => { selectedFile?.updateFileContent(markdown); + await selectedFile?.saveFileContent(); + setSelectedFile(selectedFile.getCopy()); //updating state to cause rerender (direct updates to objects/objecttree do not cause rerender) + }; + + const customUploadHandler = (files: FileWithProgress[]): DelayedImage[] => { + const delayedImages: DelayedImage[] = []; + + files.forEach(({ file, progress }) => { + const delayedImage: DelayedImage = async () => { + // Your upload logic here + // You may use XMLHttpRequest, fetch, or any other method for file upload + const blob = new Blob([file], { type: file.type }); + const blobUrl = URL.createObjectURL(blob); + + const imageAttributes: ImageExtensionAttributes = { + src: blobUrl, + fileName: file.name, // You may adjust this based on your file naming requirements + alt: file.name, // You may adjust this based on your file naming requirements + title: file.name, // You may adjust this based on your file naming requirements + }; + + // Check if the parent directory handle exists + if (selectedFile.parent?.directoryHandle) { + const parentDirectoryHandle = selectedFile.parent.directoryHandle; + + // Create a new file in the parent directory using the file name + try { + console.log( + "getting the writeable writing the blob to the file system" + ); + + const newFileHandle = await parentDirectoryHandle.getFileHandle( + file.name, + { + create: true, + } + ); + + // Write the blob data to the new file + + console.log("writing the blob to the file system"); + console.log(blob.size); + const writeAble = await newFileHandle.createWritable(); + await writeAble.write(blob); + writeAble.close(); + + selectedFile.replacedImages[blobUrl] = "./" + file.name; + } catch (e) { + console.error(e); + } + + // Return the image attributes + return imageAttributes; + } else { + // Handle the case where the parent directory handle doesn't exist + // You may want to throw an error or handle it differently based on your requirements + console.error("Parent directory handle not found."); + return null; + } + + return imageAttributes; + }; + + delayedImages.push(delayedImage); + }); + + return delayedImages; }; return ( -
-
- -
-
+
+
diff --git a/src/components/MarkdownEditor/RemirrorMarkdownEditor.tsx b/src/components/MarkdownEditor/RemirrorMarkdownEditor.tsx index 76a2dfe..5b15826 100644 --- a/src/components/MarkdownEditor/RemirrorMarkdownEditor.tsx +++ b/src/components/MarkdownEditor/RemirrorMarkdownEditor.tsx @@ -5,7 +5,7 @@ import "@remirror/styles/all.css"; -import React, { FC, PropsWithChildren, useCallback } from "react"; +import React, { FC, PropsWithChildren, useCallback, useState } from "react"; import jsx from "refractor/lang/jsx.js"; import typescript from "refractor/lang/typescript.js"; import { ExtensionPriority } from "remirror"; @@ -17,6 +17,7 @@ import { CodeExtension, HardBreakExtension, HeadingExtension, + ImageExtension, ItalicExtension, LinkExtension, ListItemExtension, @@ -37,9 +38,13 @@ import { } from "@remirror/react"; import type { CreateEditorStateProps, Extension } from "remirror"; import type { RemirrorProps, UseThemeProps } from "@remirror/react"; +import { useExtensionEvent } from "@remirror/react"; import { Menu as CustomMenu } from "./RemirrorMenu"; import { OnChangeMarkdown } from "./OnChangeMarkdown"; +import { DelayedImage, FileWithProgress } from "./RemirrorComponent"; + import "./index.css"; +import DirectoryNode from "../../models/DirectoryNode"; interface ReactEditorProps extends Pick, @@ -47,20 +52,64 @@ interface ReactEditorProps placeholder?: string; theme?: UseThemeProps["theme"]; persistMarkdown?: (markdown: string) => void; + customUploadHandler?: (files: FileWithProgress[]) => DelayedImage[]; + setSelectedFile: (file: DirectoryNode) => void; + selectedFile: DirectoryNode; } export interface MarkdownEditorProps extends Partial> {} +const OnClickLink = ({ + selectedFile, + setSelectedFile, +}: { + selectedFile: DirectoryNode; + setSelectedFile: (file: DirectoryNode) => void; +}) => { + useExtensionEvent( + LinkExtension, + "onClick", + useCallback((_, data) => { + const urlWithoutProtocol = data.href.replace(/(^\w+:|^)\/\//, ""); //remove protocol, even when remirror adds just // + const urlWithHttps = "https://" + urlWithoutProtocol; + const url = new URL(urlWithHttps); + if (!url.host.startsWith(".")) { + //open in new tab + window.open(urlWithHttps, "_blank"); // Open the link in a new tab or window + return true; + } + const filePath = decodeURI(urlWithoutProtocol); + setSelectedFile( + selectedFile.findNodeByRelativePath(filePath)?.getCopy() ?? selectedFile + ); + return true; + }, []) + ); + + return <>; +}; + /** * The editor which is used to create the annotation. Supports formatting. */ export const RemirrorMarkdownEditor: FC< PropsWithChildren -> = ({ placeholder, children, theme, persistMarkdown, ...rest }) => { +> = ({ + placeholder, + children, + theme, + persistMarkdown, + customUploadHandler, + selectedFile, + setSelectedFile, + ...rest +}) => { const extensions: () => Extension[] = useCallback( () => [ - new LinkExtension({ autoLink: true }), + new LinkExtension({ + defaultProtocol: "https://", + }), new PlaceholderExtension({ placeholder }), new BoldExtension(), new StrikeExtension(), @@ -71,7 +120,6 @@ export const RemirrorMarkdownEditor: FC< new OrderedListExtension(), new ListItemExtension({ priority: ExtensionPriority.High, - enableCollapsible: true, }), new CodeExtension(), new CodeBlockExtension({ supportedLanguages: [jsx, typescript] }), @@ -83,6 +131,9 @@ export const RemirrorMarkdownEditor: FC< * e.g. in a list item */ new HardBreakExtension(), + new ImageExtension({ + uploadHandler: customUploadHandler, + }), ], [placeholder] ); @@ -95,17 +146,20 @@ export const RemirrorMarkdownEditor: FC< return ( - {/* */} + { if (persistMarkdown) persistMarkdown(markdown); }} /> + {children} ); diff --git a/src/components/MarkdownEditor/RemirrorMarkdownToolbar.tsx b/src/components/MarkdownEditor/RemirrorMarkdownToolbar.tsx new file mode 100644 index 0000000..a5f92c8 --- /dev/null +++ b/src/components/MarkdownEditor/RemirrorMarkdownToolbar.tsx @@ -0,0 +1,37 @@ +import React, { FC } from "react"; + +import { + CommandButtonGroup, + HeadingLevelButtonGroup, + HistoryButtonGroup, +} from "../button-groups"; +import { + ToggleBlockquoteButton, + ToggleBoldButton, + ToggleCodeBlockButton, + ToggleCodeButton, + ToggleItalicButton, + ToggleStrikeButton, +} from "../buttons"; +import { Toolbar } from "./base-toolbar"; +// import { VerticalDivider } from "./vertical-divider"; + +export const MarkdownToolbar: FC = () => ( +
+ + + + + + + {/* */} + + {/* */} + + + + + + +
+); diff --git a/src/components/MarkdownEditor/RemirrorMenu.tsx b/src/components/MarkdownEditor/RemirrorMenu.tsx index ff841c5..57b86d8 100644 --- a/src/components/MarkdownEditor/RemirrorMenu.tsx +++ b/src/components/MarkdownEditor/RemirrorMenu.tsx @@ -15,7 +15,6 @@ export const Menu = ({ onClick={() => { toggleBold(); focus(); - console.log(`Current Markdown: ${JSON.stringify(getMarkdown())}`); persistMarkdown(getMarkdown()); }} style={{ fontWeight: active.bold() ? "bold" : undefined }} diff --git a/src/helpers/UseLongPress.ts b/src/helpers/UseLongPress.ts new file mode 100644 index 0000000..6ce0e21 --- /dev/null +++ b/src/helpers/UseLongPress.ts @@ -0,0 +1,60 @@ +//from https://github.com/streamich/react-use/blob/master/src/useLongPress.ts + +import { useCallback, useRef } from "react"; +import { off, on } from "./UseLongPressUtil"; + +interface Options { + isPreventDefault?: boolean; + delay?: number; +} + +const isTouchEvent = (ev: Event): ev is TouchEvent => { + return "touches" in ev; +}; + +const preventDefault = (ev: Event) => { + if (!isTouchEvent(ev)) return; + + if (ev.touches.length < 2 && ev.preventDefault) { + ev.preventDefault(); + } +}; + +const useLongPress = ( + callback: (e: TouchEvent | MouseEvent) => void, + { isPreventDefault = true, delay = 300 }: Options = {} +) => { + const timeout = useRef>(); + const target = useRef(); + + const start = useCallback( + (event: TouchEvent | MouseEvent) => { + // prevent ghost click on mobile devices + if (isPreventDefault && event.target) { + on(event.target, "touchend", preventDefault, { passive: false }); + target.current = event.target; + } + timeout.current = setTimeout(() => callback(event), delay); + }, + [callback, delay, isPreventDefault] + ); + + const clear = useCallback(() => { + // clearTimeout and removeEventListener + timeout.current && clearTimeout(timeout.current); + + if (isPreventDefault && target.current) { + off(target.current, "touchend", preventDefault); + } + }, [isPreventDefault]); + + return { + onMouseDown: (e: any) => start(e), + onTouchStart: (e: any) => start(e), + onMouseUp: clear, + onMouseLeave: clear, + onTouchEnd: clear, + } as const; +}; + +export default useLongPress; diff --git a/src/helpers/UseLongPressUtil.ts b/src/helpers/UseLongPressUtil.ts new file mode 100644 index 0000000..27468ea --- /dev/null +++ b/src/helpers/UseLongPressUtil.ts @@ -0,0 +1,29 @@ +export const noop = () => {}; + +export function on( + obj: T | null, + ...args: Parameters | [string, Function | null, ...any] +): void { + if (obj && obj.addEventListener) { + obj.addEventListener( + ...(args as Parameters) + ); + } +} + +export function off( + obj: T | null, + ...args: + | Parameters + | [string, Function | null, ...any] +): void { + if (obj && obj.removeEventListener) { + obj.removeEventListener( + ...(args as Parameters) + ); + } +} + +export const isBrowser = typeof window !== "undefined"; + +export const isNavigator = typeof navigator !== "undefined"; diff --git a/src/models/DirectoryNode.ts b/src/models/DirectoryNode.ts index e316972..7de8a00 100644 --- a/src/models/DirectoryNode.ts +++ b/src/models/DirectoryNode.ts @@ -1,14 +1,15 @@ class DirectoryNode { + id: string; name: string; isExpanded: boolean; children: DirectoryNode[]; - fileContent?: string; // Store file content here when loaded + fileContent: string; // Store file content here when loaded unsavedChanges: boolean; // Indicator for unsaved changes - fileHandle?: FileSystemFileHandle; directoryHandle?: FileSystemDirectoryHandle; parent?: DirectoryNode; - fullPath?: string; + blobUrl?: string; + replacedImages: { [key: string]: string } = {}; constructor( name: string, @@ -17,7 +18,11 @@ class DirectoryNode { fileHandle?: FileSystemFileHandle, directoryHandle?: FileSystemDirectoryHandle, parent?: DirectoryNode, - fullPath?: string + fileContent?: string, + unsavedChanges?: boolean, + id?: string, + blobUrl?: string, + replacedImages?: { [key: string]: string } ) { this.name = name; this.isExpanded = isExpanded; @@ -25,25 +30,36 @@ class DirectoryNode { this.fileHandle = fileHandle; this.directoryHandle = directoryHandle; this.parent = parent; - this.fullPath = fullPath; this.fileContent = ""; this.unsavedChanges = false; + this.id = id || Math.random().toString(36).substr(2, 9); + this.blobUrl = blobUrl; + this.replacedImages = replacedImages || {}; + } + + getId(): string { + return this.id; + } + + getName(): string | null { + return ( + removeMdExtension(this.fileHandle?.name) || + removeMdExtension(this.directoryHandle?.name) || + null + ); } getFullPath(): string { if (this.parent) { - console.log("in this parent"); const parentPath = this.parent.getFullPath(); return `${parentPath}/${this.name}`; } if (this.directoryHandle) { - console.log("in direcotry handler"); return this.directoryHandle.name; } if (this.fileHandle) { - console.log("in file handler"); return `${this.name}`; } @@ -58,8 +74,12 @@ class DirectoryNode { try { const file = await this.fileHandle.getFile(); const content = await file.text(); - this.fileContent = content; - return content; + + const updatedMarkdownContent = + this.replaceRelativeLinksWithBlobURLs(content); + + this.fileContent = updatedMarkdownContent; + return updatedMarkdownContent; } catch (error) { console.error("Error reading file content:", error); return null; @@ -72,20 +92,453 @@ class DirectoryNode { } } - updateFileContent(content: string): void { - console.log("in update file content", content); + updateFileContent(content: string): DirectoryNode { this.fileContent = content; this.unsavedChanges = true; // Set the indicator for unsaved changes + + // Update the children of the parent if it exists + if (this.parent && this.parent.children) { + this.parent.children = this.parent.children.map((child) => { + if (child.name === this.name) { + return this; + } else { + return child; + } + }); + } + + return this; } async saveFileContent(): Promise { if (this.fileHandle) { const writable = await this.fileHandle.createWritable(); - await writable.write(this.fileContent || ""); + + //TODO: change this to use the updated markdown content + const fileContentReadyToSave = this.replaceBlobURLsWithRelativeLinks( + this.fileContent + ); + + await writable.write(fileContentReadyToSave || ""); await writable.close(); - this.unsavedChanges = false; // Reset the indicator after saving } + + this.unsavedChanges = false; // Reset the indicator for unsaved changes + + // Update the children of the parent if it exists + if (this.parent && this.parent.children) { + this.parent.children = this.parent.children.map((child) => { + if (child.getId() === this.getId()) { + return this; + } else { + return child; + } + }); + } + } + + async renameFile(newName: string): Promise { + console.log("in rename file, renaming to:", newName); + + const newFileName = addMdExtension(newName) || newName; + + console.log("new file name:", newFileName); + + if (this.fileHandle && this.parent) { + //search the parent to see if the file with the new name already exists + let existingFileHandle; + try { + existingFileHandle = await this.parent.directoryHandle?.getFileHandle( + newFileName, + { + create: false, + } + ); + } catch (error) { + if (existingFileHandle) { + console.log("returning error and exiting"); + return new Error("File already exists"); //not expected to happen since it doesn't fail when it finds the file + } else { + console.log("ignoring error and creating file"); + + //do nothing, error is thrown when we attempt to get file handler but no file is present + } + } + + if (existingFileHandle) { + console.log("returning error and exiting"); + return new Error("File already exists"); //not expected to happen since it doesn't fail when it finds the file + } + + await this.fileHandle.move(newFileName); + + console.log("renamed file, updating name", this.fileHandle.name); + + //update the children of the parent + this.parent.children = this.parent.children.map((child) => { + if (child.name === this.name) { + return this; + } else { + return child; + } + }); + } + } + + getCopy(): DirectoryNode { + return new DirectoryNode( + this.name, + this.isExpanded, + this.children, + this.fileHandle, + this.directoryHandle, + this.parent, + this.fileContent, + this.unsavedChanges, + this.id, + this.blobUrl, + this.replacedImages + ); + } + + // Helper method to check if the node represents a directory + isDirectory(): boolean { + return !!this.directoryHandle; + } + + replaceBlobURLsWithRelativeLinks = (markdownContent: string) => { + const regex = /!\[(.*?)\]\((.*?)\s?("(.*?)")?\)/g; + + const updatedMarkdownContent = markdownContent.replaceAll( + regex, + (match, altText, blobUrl) => { + // imagePath now contains the relative path to the image + // Use this information to construct the blob URL or any other logic + // Here, I'm assuming you have a map of relative paths to blob URLs in your DirectoryNode + const relativeFilePath = this.replacedImages[blobUrl]; + + // If a blob URL is found, use it; otherwise, keep the original relative link + return relativeFilePath ? `![${altText}](${relativeFilePath})` : match; + } + ); + + return updatedMarkdownContent; + }; + + replaceRelativeLinksWithBlobURLs = (markdownContent: string) => { + console.log("called replace relative links"); + const regex = /!\[(.*?)\]\((.*?)\s?("(.*?)")?\)/g; + + const updatedMarkdownContent = markdownContent.replaceAll( + regex, + (match, altText, imagePath) => { + console.log("in replace relative links", match, altText, imagePath); + // imagePath now contains the relative path to the image + // Use this information to construct the blob URL or any other logic + // Here, I'm assuming you have a map of relative paths to blob URLs in your DirectoryNode + const blobUrl = this.findNodeByRelativePath(imagePath)?.blobUrl; + + if (blobUrl) { + this.replacedImages[blobUrl] = imagePath; + } + + console.log(blobUrl); + + // If a blob URL is found, use it; otherwise, keep the original relative link + return blobUrl ? `![${altText}](${blobUrl})` : match; + } + ); + + return updatedMarkdownContent; + }; + + findNodeByRelativePath = ( + relativePath: string + ): DirectoryNode | undefined => { + console.log("relative path:", relativePath); + // Split the relative path into individual segments + const pathSegments = relativePath.split("/"); + + // Start from the current node + let currentNode: DirectoryNode | undefined = this.parent; + + for (const segment of pathSegments) { + // Handle parent directory indicator ".." + if (segment === ".") { + continue; + } + if (segment === "..") { + if (!currentNode?.parent) { + // If the current node does not have a parent, return null, we weren't able to find the node + return undefined; + } + currentNode = currentNode?.parent; + } else { + // Find the child node with the matching name + const matchingChild = currentNode?.children.find( + (child) => child.name === segment + ); + + if (matchingChild?.isDirectory()) { + currentNode = matchingChild; + continue; + } + + if (!matchingChild) { + // If the child is not found or is not a directory, return undefined + return undefined; + } + + currentNode = matchingChild; + } + } + + return currentNode; + }; + + async delete(skipConfirmation?: boolean): Promise { + //return parent if it exists + if (!skipConfirmation) { + //ask the user to confirm deletion + const confirmation = confirm( + `Are you sure you want to delete ${this.name}? This cannot be reversed.` + ); + + if (!confirmation) { + return; + } + } + + //if this is a file, delete it, use removeEntry on the parent + if (this.fileHandle) { + await this.parent?.directoryHandle?.removeEntry(this.fileHandle.name); + } + + //if this is a folder, ask the + if (this.directoryHandle) { + //delete all the files in the folder + for (const child of this.children) { + await child.delete(true); + } + + //delete the folder + await this.parent?.directoryHandle?.removeEntry( + this.directoryHandle.name + ); + } + + // Update the children of the parent if it exists + if (this.parent && this.parent.children) { + let tempChildren = []; + for (const child of this.parent.children) { + if (child.getId() !== this.getId()) { + tempChildren.push(child); + } + } + console.log(tempChildren); + this.parent.children = tempChildren; + } + + return this.parent; + } + + async createFile(fileName: string): Promise { + if (!this.directoryHandle) { + throw new Error("Directory handle not found"); + } + + //try to get the file to see if it already exists + let existingFileHandle; + try { + existingFileHandle = await this.directoryHandle?.getFileHandle(fileName, { + create: false, + }); + } catch (error) { + //do nothing, error is thrown when we attempt to get file handler but no file is present which is expected + } + + if (existingFileHandle) { + throw new Error("File already exists"); //not expected to happen since it doesn't fail when it finds the file + return; + } + + //search the parent to see if the file with the new name already exists + let newFileHandle; + try { + newFileHandle = await this.directoryHandle.getFileHandle(fileName, { + create: true, + }); + } catch (error) { + if (newFileHandle) { + throw new Error("File already exists"); //not expected to happen since it doesn't fail when it finds the file + } + } + + //create the DirectoryNode for the new file + const newFileNode = new DirectoryNode( + fileName, + false, + [], + newFileHandle, + undefined, + this + ); + + //add the new file to the parent + this.children.push(newFileNode); + + return newFileNode; + } + + async createFolder(folderName: string): Promise { + console.log("in create folder"); + if (!this.directoryHandle) { + throw new Error("Directory handle not found"); + } + + //try to get the folder to see if it already exists + let existingFolderHandle; + try { + existingFolderHandle = await this.directoryHandle?.getDirectoryHandle( + folderName, + { + create: false, + } + ); + } catch (error) { + //do nothing, error is thrown when we attempt to get file handler but no file is present which is expected + } + + if (existingFolderHandle) { + throw new Error("Folder already exists"); //not expected to happen since it doesn't fail when it finds the file + return; + } + + //search the parent to see if the file with the new name already exists + let newFolderHandle; + try { + newFolderHandle = await this.directoryHandle.getDirectoryHandle( + folderName, + { + create: true, + } + ); + } catch (error) { + if (newFolderHandle) { + throw new Error("Folder already exists"); //not expected to happen since it doesn't fail when it finds the file + } + } + + //create the DirectoryNode for the new file + const newFolderNode = new DirectoryNode( + folderName, + false, + [], + undefined, + newFolderHandle, + this + ); + + //add the new file to the parent + this.children.push(newFolderNode); + + return newFolderNode; } } export default DirectoryNode; + +// Add ".md" extension to a file name +const addMdExtension = (fileName: string | undefined) => { + if (!fileName) { + return null; + } + return fileName.endsWith(".md") ? fileName : `${fileName}.md`; +}; + +// Remove ".md" extension from a file name +const removeMdExtension = (fileName: string | undefined) => { + if (!fileName) { + return null; + } + return fileName.endsWith(".md") ? fileName.slice(0, -3) : fileName; +}; + +export const createDirectoryNode = async ( + directoryHandle: FileSystemDirectoryHandle, + parent?: DirectoryNode +): Promise => { + const entries: DirectoryNode[] = []; + + let directoryNode = new DirectoryNode( + directoryHandle.name, + false, + entries, + undefined, + directoryHandle, + parent + ); + + //@ts-ignore + for await (const entry of directoryHandle.values()) { + console.log("getting all entries"); + console.log(entry); + const acceptedFileExtensions = [ + ".md", + ".png", + ".jpg", + ".jpeg", + ".gif", + ".svg", + ]; + + if ( + entry.kind === "file" && + !acceptedFileExtensions.some((extension) => + entry.name.endsWith(extension) + ) + ) { + continue; + } + + if (entry.kind === "directory") { + const subdirectoryHandle = entry as FileSystemDirectoryHandle; + const subdirectoryNode = await createDirectoryNode( + subdirectoryHandle, + directoryNode + ); + entries.push(subdirectoryNode); + } else { + const fileHandle = entry as FileSystemFileHandle; + + const file = await fileHandle.getFile(); + + console.log(file); + let blobUrl = undefined; + if (file.type.startsWith("image/")) { + //create a blob url for the image + const blob = new Blob([file], { type: file.type }); + blobUrl = URL.createObjectURL(blob); + console.log(blobUrl); + } + + const fileNode = new DirectoryNode( + entry.name, + false, + [], + fileHandle, + undefined, + directoryNode, + undefined, + undefined, + undefined, + blobUrl + ); + entries.push(fileNode); + } + } + + directoryNode.children = entries; + + return directoryNode; +}; diff --git a/src/pages/Editor.tsx b/src/pages/Editor.tsx index d9b0f7e..744ebe4 100644 --- a/src/pages/Editor.tsx +++ b/src/pages/Editor.tsx @@ -1,26 +1,61 @@ import { useState } from "react"; import { LocalFileSystem } from "../components/FileSystemAdapters/FileSystem/LocalFileSystem"; import { FileEditor } from "../components/MarkdownEditor/FileEditor"; -import DirectoryNode from "../models/DirectoryNode"; +import DirectoryNode, { createDirectoryNode } from "../models/DirectoryNode"; export const EditorPage: React.FC = () => { - const [selectedFile, setSelectedFile] = useState(null); // [selectedFile, setSelectedFile - const [selectedFileName, setSelectedFileName] = useState(null); - const [selectedFileContent, setSelectedFileContent] = useState( - null - ); + const [selectedFile, setSelectedFile] = useState( + undefined + ); // [selectedFile, setSelectedFile + const [selectedDirectory, setSelectedDirectory] = + useState(null); + const [contextMenuVisible, setContextMenuVisible] = useState(false); + const [contextMenuPosition, setContextMenuPosition] = useState({ + x: 0, + y: 0, + }); + + const handleSetSelectedFile = async (node: DirectoryNode) => { + await node.loadFileContent(); + setSelectedFile(node); + }; return ( -
+
{ + setContextMenuVisible(false); + }} + onContextMenu={(e) => { + setContextMenuVisible(false); + }} + > - +
+
+
+ {selectedFile?.blobUrl ? ( +
+ +
+ ) : ( + + )} +
+
+
); }; diff --git a/tailwind.config.js b/tailwind.config.js index 614c86b..e78605b 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -4,5 +4,5 @@ export default { theme: { extend: {}, }, - plugins: [], + plugins: [require("@tailwindcss/typography")], }; diff --git a/tsconfig.json b/tsconfig.json index a7fc6fb..a938959 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "target": "ES2020", "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], + "lib": ["ES2021", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true,