diff --git a/README.md b/README.md index 7721325..882a18e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Prozilla OS +# Prozilla OS Prozilla OS is a web-based operating system inspired by Ubuntu Linux and Windows made with React by Prozilla. diff --git a/docs/features/apps/file-explorer/README.md b/docs/features/apps/file-explorer/README.md index c36a416..31a5cb5 100644 --- a/docs/features/apps/file-explorer/README.md +++ b/docs/features/apps/file-explorer/README.md @@ -1,6 +1,6 @@ [← Back](../README.md) -# File Explorer ("Files") +# File Explorer ("Files") A tool for exploring and managing files and folders with a user interface. diff --git a/docs/features/apps/media-viewer/README.md b/docs/features/apps/media-viewer/README.md index 48e9e61..396366c 100644 --- a/docs/features/apps/media-viewer/README.md +++ b/docs/features/apps/media-viewer/README.md @@ -1,6 +1,6 @@ [← Back](../README.md) -# Media Viewer ("Photos") +# Media Viewer ("Photos") An app for viewing media like images and videos. diff --git a/docs/features/apps/settings/README.md b/docs/features/apps/settings/README.md index 326c91b..e61f9c6 100644 --- a/docs/features/apps/settings/README.md +++ b/docs/features/apps/settings/README.md @@ -1,6 +1,6 @@ [← Back](../README.md) -# Settings +# Settings Allows the user to change their settings and personalize their instance of Prozilla OS. diff --git a/docs/features/apps/terminal/README.md b/docs/features/apps/terminal/README.md index ee218ec..8d9c675 100644 --- a/docs/features/apps/terminal/README.md +++ b/docs/features/apps/terminal/README.md @@ -1,6 +1,6 @@ [← Back](../README.md) -# Terminal ("Commands") +# Terminal ("Commands") A command line tool. diff --git a/docs/features/apps/text-editor/README.md b/docs/features/apps/text-editor/README.md index 217f598..8daacaa 100644 --- a/docs/features/apps/text-editor/README.md +++ b/docs/features/apps/text-editor/README.md @@ -1,6 +1,6 @@ [← Back](../README.md) -# Text Editor ("Notes") +# Text Editor ("Notes") An app for editing text documents. diff --git a/package-lock.json b/package-lock.json index 90db0ba..627ff44 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "react-draggable": "^4.4.5", "react-scripts": "5.0.1", "react-svg": "^16.1.18", + "react-syntax-highlighter": "^15.5.0", "react-tabs": "^6.0.2", "web-vitals": "^2.1.4" }, @@ -4148,6 +4149,14 @@ "@types/node": "*" } }, + "node_modules/@types/hast": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.8.tgz", + "integrity": "sha512-aMIqAlFd2wTIDZuvLbhUT+TGvMxrNC8ECUIVtH6xxy0sQLs3iu6NO8Kp/VT5je7i5ufnebXzdV1dNDMnvaH6IQ==", + "dependencies": { + "@types/unist": "^2" + } + }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -4568,6 +4577,11 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz", "integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==" }, + "node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, "node_modules/@types/ws": { "version": "8.5.5", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz", @@ -5992,6 +6006,33 @@ "node": ">=10" } }, + "node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/check-types": { "version": "11.2.2", "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.2.tgz", @@ -6159,6 +6200,15 @@ "node": ">= 0.8" } }, + "node_modules/comma-separated-tokens": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", + "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", @@ -8255,6 +8305,18 @@ "reusify": "^1.0.4" } }, + "node_modules/fault": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/faye-websocket": { "version": "0.11.4", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", @@ -8650,6 +8712,14 @@ "node": ">= 6" } }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -9131,6 +9201,31 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hast-util-parse-selector": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", + "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz", + "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -9139,6 +9234,14 @@ "he": "bin/he" } }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "engines": { + "node": "*" + } + }, "node_modules/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -9527,6 +9630,28 @@ "node": ">= 10" } }, + "node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -9648,6 +9773,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-docker": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", @@ -9697,6 +9831,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-map": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", @@ -12310,6 +12453,19 @@ "tslib": "^2.0.3" } }, + "node_modules/lowlight": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", + "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", + "dependencies": { + "fault": "^1.0.0", + "highlight.js": "~10.7.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -13018,6 +13174,23 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -14514,6 +14687,14 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -14554,6 +14735,18 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/property-information": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", + "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", + "dependencies": { + "xtend": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -14973,6 +15166,21 @@ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-syntax-highlighter": { + "version": "15.5.0", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz", + "integrity": "sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "highlight.js": "^10.4.1", + "lowlight": "^1.17.0", + "prismjs": "^1.27.0", + "refractor": "^3.6.0" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, "node_modules/react-tabs": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-6.0.2.tgz", @@ -15048,6 +15256,28 @@ "node": ">=8" } }, + "node_modules/refractor": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz", + "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==", + "dependencies": { + "hastscript": "^6.0.0", + "parse-entities": "^2.0.0", + "prismjs": "~1.27.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/prismjs": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", + "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==", + "engines": { + "node": ">=6" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -15848,6 +16078,15 @@ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", "deprecated": "Please use @jridgewell/sourcemap-codec instead" }, + "node_modules/space-separated-tokens": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/spdx-exceptions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", @@ -17868,6 +18107,14 @@ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 7c58653..6e66bb6 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "react-draggable": "^4.4.5", "react-scripts": "5.0.1", "react-svg": "^16.1.18", + "react-syntax-highlighter": "^15.5.0", "react-tabs": "^6.0.2", "web-vitals": "^2.1.4" }, diff --git a/src/components/actions/actions/ClickAction.jsx b/src/components/actions/actions/ClickAction.jsx index 395b64f..0524566 100644 --- a/src/components/actions/actions/ClickAction.jsx +++ b/src/components/actions/actions/ClickAction.jsx @@ -4,6 +4,14 @@ import styles from "../Actions.module.css"; import { ImagePreview } from "../../apps/file-explorer/directory-list/ImagePreview.jsx"; import { memo } from "react"; +/** + * @param {object} props + * @param {string} props.actionId + * @param {string} props.label + * @param {string[]} props.shortcut + * @param {Function} props.onTrigger + * @param {string|object} props.icon + */ export const ClickAction = memo(({ actionId, label, shortcut, onTrigger, icon }) => { return ( diff --git a/src/components/actions/actions/DropdownAction.jsx b/src/components/actions/actions/DropdownAction.jsx index 081b75c..4edfa57 100644 --- a/src/components/actions/actions/DropdownAction.jsx +++ b/src/components/actions/actions/DropdownAction.jsx @@ -3,6 +3,12 @@ import styles from "../Actions.module.css"; import { faCaretRight } from "@fortawesome/free-solid-svg-icons"; import { useState } from "react"; +/** + * @param {object} props + * @param {string} props.label + * @param {string|object} props.icon + * @param {*} props.children + */ export function DropdownAction({ label, icon, children }) { const [showContent, setShowContent] = useState(false); diff --git a/src/components/actions/actions/ToggleAction.jsx b/src/components/actions/actions/ToggleAction.jsx index fbe35ef..3a27d6e 100644 --- a/src/components/actions/actions/ToggleAction.jsx +++ b/src/components/actions/actions/ToggleAction.jsx @@ -5,6 +5,14 @@ import { useState } from "react"; import { faSquare } from "@fortawesome/free-regular-svg-icons"; import { faSquareCheck } from "@fortawesome/free-solid-svg-icons"; +/** + * @param {object} props + * @param {string} props.actionId + * @param {string} props.label + * @param {string[]} props.shortcut + * @param {Function} props.onTrigger + * @param {boolean} props.initialValue + */ export function ToggleAction({ actionId, label, shortcut, initialValue, onTrigger }) { const [active, setActive] = useState(initialValue ?? false); diff --git a/src/components/apps/file-explorer/FileExplorer.jsx b/src/components/apps/file-explorer/FileExplorer.jsx index 3561692..8af3d58 100644 --- a/src/components/apps/file-explorer/FileExplorer.jsx +++ b/src/components/apps/file-explorer/FileExplorer.jsx @@ -20,6 +20,7 @@ import { TITLE_SEPARATOR } from "../../../config/windows.config.js"; import { FileProperties } from "../../modals/file-properties/FileProperties.jsx"; import { useHistory } from "../../../hooks/_utils/history.js"; import { Divider } from "../../actions/actions/Divider.jsx"; +import { CODE_FORMATS } from "../../../config/apps/textEditor.config.js"; /** * @param {import("../../windows/WindowView.jsx").windowProps} props @@ -223,7 +224,7 @@ export function FileExplorer({ startPath, app, modalsManager }) { onClickFile={(event, file) => { event.preventDefault(); const options = {}; - if (file.extension === "md") + if (file.extension === "md" || CODE_FORMATS.includes(file.extension)) options.mode = "view"; windowsManager.openFile(file, options); }} diff --git a/src/components/apps/text-editor/TextEditor.jsx b/src/components/apps/text-editor/TextEditor.jsx index 9ecf6c1..b7203f1 100644 --- a/src/components/apps/text-editor/TextEditor.jsx +++ b/src/components/apps/text-editor/TextEditor.jsx @@ -1,15 +1,26 @@ -import React, { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import styles from "./TextEditor.module.css"; import { HeaderMenu } from "../_utils/header-menu/HeaderMenu.jsx"; import Markdown from "markdown-to-jsx"; -import { DEFAULT_ZOOM, ZOOM_FACTOR } from "../../../config/apps/textEditor.config.js"; +import { CODE_FORMATS, DEFAULT_ZOOM, EXTENSION_TO_LANGUAGE, ZOOM_FACTOR } from "../../../config/apps/textEditor.config.js"; import AppsManager from "../../../features/apps/appsManager.js"; import { TITLE_SEPARATOR } from "../../../config/windows.config.js"; +import { MarkdownLink } from "./overrides/MarkdownLink.jsx"; +import { MarkdownImage } from "./overrides/MarkdownImage.jsx"; +import { useWindowsManager } from "../../../hooks/windows/windowsManagerContext.js"; +import SyntaxHighlighter from "react-syntax-highlighter"; + +const OVERRIDES = { + a: MarkdownLink, + img: MarkdownImage, +}; /** * @param {import("../../windows/WindowView.jsx").windowProps} props */ -export function TextEditor({ file, setTitle, setIconUrl, close, mode, app }) { +export function TextEditor({ file, setTitle, setIconUrl, close, mode, app, modalsManager }) { + const ref = useRef(); + const windowsManager = useWindowsManager(); const [currentFile, setCurrentFile] = useState(file); const [currentMode, setCurrentMode] = useState(mode); const [content, setContent] = useState(file?.content ?? ""); @@ -40,6 +51,10 @@ export function TextEditor({ file, setTitle, setIconUrl, close, mode, app }) { } setContent(newContent); + + if (ref.current) { + ref.current.scrollTo(0, 0); + } })(); }, [app.id, currentFile, setIconUrl]); @@ -86,6 +101,20 @@ export function TextEditor({ file, setTitle, setIconUrl, close, mode, app }) { return setContent(value); }; + const overrides = {}; + for (const [key, value] of Object.entries(OVERRIDES)) { + overrides[key] = { + component: value, + props: { + modalsManager, + setCurrentFile, + currentFile, + app, + windowsManager + } + }; + } + return ( {currentMode === "view" - ? - {file?.extension === "md" - ? - {content} - - : {content} - } - + ? CODE_FORMATS.includes(file?.extension) + ? {content} + : + {file?.extension === "md" + ? {content} + : {content} + } + : p { + width: fit-content; + margin: 0; +} + +.View code { + font-family: var(--mono-font-family); + border-radius: 0.5rem; + background-color: var(--background-color-b); + padding: 0.25rem; +} + +.View table { + border-collapse: collapse; +} + +.View table, .View th, .View td { + border: 0.2rem solid var(--background-color-a); +} + +.View th, .View td { + padding: 0.5rem; +} + +.Code { + margin: 0; + cursor: default; +} + +.Code *[class*="hljs-string"] { color: var(--green-a); } +.Code *[class*="hljs-literal"] { color: var(--yellow-a); } +.Code *[class*="hljs-number"] { color: var(--yellow-a); } +.Code *[class*="hljs-keyword"] { color: var(--blue-a); } +.Code *[class*="hljs-title"] { color: var(--cyan-a); } +.Code *[class*="hljs-name"] { color: var(--red-a); } +.Code *[class*="hljs-attr"] { color: var(--blue-a); } +.Code *[class*="hljs-param"] { color: var(--red-a); } +.Code *[class*="hljs-comment"] { color: var(--dark-grey-a); } +.Code *[class*="linenumber"] { color: var(--dark-grey-a); } +.Code *[class*="hljs-meta"] { color: var(--blue-a); } \ No newline at end of file diff --git a/src/components/apps/text-editor/overrides/MarkdownImage.jsx b/src/components/apps/text-editor/overrides/MarkdownImage.jsx new file mode 100644 index 0000000..fcf25b0 --- /dev/null +++ b/src/components/apps/text-editor/overrides/MarkdownImage.jsx @@ -0,0 +1,30 @@ +import { useMemo } from "react"; +import { VirtualFile } from "../../../../features/virtual-drive/file/virtualFile.js"; +import ModalsManager from "../../../../features/modals/modalsManager.js"; +import App from "../../../../features/apps/app.js"; +import WindowsManager from "../../../../features/windows/windowsManager.js"; + +/** + * @param {object} props + * @param {VirtualFile} props.currentFile + * @param {string} props.src + * @param {string} props.alt + * @param {ModalsManager} props.modalsManager + * @param {WindowsManager} props.windowsManager + * @param {App} props.app + */ +export function MarkdownImage({ currentFile, alt, src, modalsManager, windowsManager, app, ...props }) { + const source = useMemo(() => { + if (src.startsWith("public")) { + return src.replace(/^public\//g, `${process.env.PUBLIC_URL}/`); + } + + return src; + }, [src]); + + return ; +} \ No newline at end of file diff --git a/src/components/apps/text-editor/overrides/MarkdownLink.jsx b/src/components/apps/text-editor/overrides/MarkdownLink.jsx new file mode 100644 index 0000000..9911f94 --- /dev/null +++ b/src/components/apps/text-editor/overrides/MarkdownLink.jsx @@ -0,0 +1,85 @@ +import { useMemo } from "react"; +import { faExternalLink } from "@fortawesome/free-solid-svg-icons"; +import { useContextMenu } from "../../../../hooks/modals/contextMenu.js"; +import { Actions } from "../../../actions/Actions.jsx"; +import { ClickAction } from "../../../actions/actions/ClickAction.jsx"; +import ModalsManager from "../../../../features/modals/modalsManager.js"; +import { VirtualFile } from "../../../../features/virtual-drive/file/virtualFile.js"; +import { useWindowedModal } from "../../../../hooks/modals/windowedModal.js"; +import AppsManager from "../../../../features/apps/appsManager.js"; +import App from "../../../../features/apps/app.js"; +import { DialogBox } from "../../../modals/dialog-box/DialogBox.jsx"; +import { DIALOG_CONTENT_TYPES } from "../../../../config/modals.config.js"; +import Vector2 from "../../../../features/math/vector2.js"; +import WindowsManager from "../../../../features/windows/windowsManager.js"; +import { APPS } from "../../../../config/apps.config.js"; + +/** + * @param {object} props + * @param {string} props.href + * @param {*} props.children + * @param {ModalsManager} props.modalsManager + * @param {WindowsManager} props.windowsManager + * @param {Function} props.setCurrentFile + * @param {VirtualFile} props.currentFile + * @param {App} props.app + */ +export function MarkdownLink({ href, children, modalsManager, windowsManager, currentFile, setCurrentFile, app, ...props }) { + const { openWindowedModal } = useWindowedModal({ modalsManager }); + + const onClick = (event) => { + event.preventDefault(); + + if (!href.startsWith("http://") && !href.startsWith("https://")) { + const target = currentFile.parent.navigate(href); + if (target) { + if (target.isFile()) { + setCurrentFile(target); + } else { + windowsManager.open(APPS.FILE_EXPLORER, { startPath: target.path }); + } + } else { + openWindowedModal({ + iconUrl: AppsManager.getAppIconUrl(app.id), + title: "Failed to open link", + size: new Vector2(450, 150), + Modal: (props) => + + Target not found: "{href}" + Ok + + }); + } + } else { + window.open(href, "_blank").focus(); + } + }; + + const { onContextMenu } = useContextMenu({ modalsManager, Actions: (props) => + + + + }); + + const title = useMemo(() => { + let title = null; + try { + title = new URL(href).hostname; + } catch (error) { + title = href.split("/").pop(); + } + return title; + }, [href]); + + return + {children} + ; +} \ No newline at end of file diff --git a/src/config/apps/textEditor.config.js b/src/config/apps/textEditor.config.js index 4c985b9..9d719e5 100644 --- a/src/config/apps/textEditor.config.js +++ b/src/config/apps/textEditor.config.js @@ -1,2 +1,17 @@ export const DEFAULT_ZOOM = 16; -export const ZOOM_FACTOR = 4; \ No newline at end of file +export const ZOOM_FACTOR = 4; +export const CODE_FORMATS = [ + "js", + "jsx", + "json", + "css", + "html", + "xml", + "yml" +]; + +export const EXTENSION_TO_LANGUAGE = { + "js": "javascript", + "jsx": "javascript", + "yml": "yaml", +}; \ No newline at end of file diff --git a/src/styles/global.css b/src/styles/global.css index acd68ee..4d25143 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -9,6 +9,7 @@ html, body, #root { } html { + scroll-behavior: smooth; overflow: hidden; background-color: var(--background-color-c); font-size: 16px;
{content}
Target not found: "{href}"