Added markdown preview for text editor

This commit is contained in:
Prozilla 2023-07-31 19:09:50 +02:00
parent 73083693bb
commit a26e3a3809
No known key found for this signature in database
GPG key ID: 5858DFE71CAF31EE
8 changed files with 102 additions and 16 deletions

12
package-lock.json generated
View file

@ -16,6 +16,7 @@
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"core-js": "^3.31.1", "core-js": "^3.31.1",
"markdown-to-jsx": "^7.2.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-draggable": "^4.4.5", "react-draggable": "^4.4.5",
@ -12259,6 +12260,17 @@
"tmpl": "1.0.5" "tmpl": "1.0.5"
} }
}, },
"node_modules/markdown-to-jsx": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.2.1.tgz",
"integrity": "sha512-9HrdzBAo0+sFz9ZYAGT5fB8ilzTW+q6lPocRxrIesMO+aB40V9MgFfbfMXxlGjf22OpRy+IXlvVaQenicdpgbg==",
"engines": {
"node": ">= 10"
},
"peerDependencies": {
"react": ">= 0.14.0"
}
},
"node_modules/mdn-data": { "node_modules/mdn-data": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz",

View file

@ -14,6 +14,7 @@
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"core-js": "^3.31.1", "core-js": "^3.31.1",
"markdown-to-jsx": "^7.2.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-draggable": "^4.4.5", "react-draggable": "^4.4.5",

13
public/documents/info.md Normal file
View file

@ -0,0 +1,13 @@
<img src="/media/Banner2.png" width="100%" height="200" alt="Banner with ProzillaOS' name and logo"/>
# Info
This is ProzillaOS, a web-based operating system made with React.js by Prozilla.
## Open Source
ProzillaOS is [open source](https://github.com/Prozilla/Prozilla-OS)! Feel free to fork this project and create your own OS.
## Support ProzillaOS
You can support this project by donating to [ko-fi.com/prozilla](https://ko-fi.com/prozilla).

View file

@ -21,6 +21,7 @@ function FilePreview({ file }) {
</div>) </div>)
break; break;
case "txt": case "txt":
case "md":
preview = <FontAwesomeIcon icon={faFileLines}/> preview = <FontAwesomeIcon icon={faFileLines}/>
break; break;
default: default:

View file

@ -1,28 +1,55 @@
import { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
import { VirtualFile } from "../../../features/virtual-drive/virtual-file.js"; import { VirtualFile } from "../../../features/virtual-drive/virtual-file.js";
import styles from "./TextEditor.module.css"; import styles from "./TextEditor.module.css";
import { HeaderMenu } from "../.common/HeaderMenu.jsx"; import { HeaderMenu } from "../.common/HeaderMenu.jsx";
import Markdown from "markdown-to-jsx";
/** /**
* @param {Object} props * @param {Object} props
* @param {VirtualFile} props.file * @param {VirtualFile} props.file
*/ */
export function TextEditor({ file, setTitle, close }) { export function TextEditor({ file, setTitle, close, mode }) {
const [currentFile, setCurrentFile] = useState(file); const [currentFile, setCurrentFile] = useState(file);
const [content, setContent] = useState(file?.content); const [currentMode, setCurrentMode] = useState(mode);
const [content, setContent] = useState(file?.content ?? "");
const [unsavedChanges, setUnsavedChanges] = useState(false); const [unsavedChanges, setUnsavedChanges] = useState(false);
useEffect(() => { useEffect(() => {
setContent(currentFile?.content ?? ""); (async () => {
let newContent = "";
if (currentFile) {
if (currentFile.content) {
newContent = currentFile.content;
} else if (currentFile.source) {
await fetch(currentFile.source).then((response) =>
response.text()
).then((response) => {
newContent = response;
});
}
}
setContent(newContent);
})();
}, [currentFile]); }, [currentFile]);
useEffect(() => { useEffect(() => {
setTitle(`${currentFile?.id ?? "Untitled"}${unsavedChanges ? "*" : ""} - Text Editor`); let label = currentFile?.id ?? "Untitled";
}, [currentFile, setTitle, unsavedChanges]);
if (unsavedChanges)
label += "*";
if (currentMode === "view")
label += " (preview)";
setTitle(`${label} - Text Editor`);
}, [currentFile, setTitle, unsavedChanges, currentMode]);
const newText = () => { const newText = () => {
setCurrentFile(null); setCurrentFile(null);
setCurrentMode("edit");
} }
const saveTextAs = () => { const saveTextAs = () => {
@ -64,15 +91,28 @@ export function TextEditor({ file, setTitle, close }) {
onSaveAs={saveTextAs} onSaveAs={saveTextAs}
onExit={() => { close(); }} onExit={() => { close(); }}
/> />
<textarea {currentMode === "view"
className={styles.View} ? <div className={styles.View}>
value={content} <Markdown options={{ overrides: {
onChange={onChange} a: {
onKeyDown={onKeyDown} props: {
spellCheck={false} target: "_blank"
autoComplete="off" }
autoFocus }
/> } }}>
{content}
</Markdown>
</div>
: <textarea
className={styles.View}
value={content}
onChange={onChange}
onKeyDown={onKeyDown}
spellCheck={false}
autoComplete="off"
autoFocus
/>
}
</div> </div>
); );
} }

View file

@ -1,6 +1,9 @@
.Container { .Container {
display: flex;
flex-direction: column;
width: 100%; width: 100%;
height: 100%; height: 100%;
text-align: start;
} }
.View { .View {
@ -14,4 +17,11 @@
font-size: 1rem; font-size: 1rem;
font-family: var(--body-font-family); font-family: var(--body-font-family);
resize: none; resize: none;
overflow: auto;
}
.View img {
max-width: 100%;
object-fit: cover;
border-radius: 0.5rem;
} }

View file

@ -6,9 +6,11 @@ import ApplicationsManager from "../../features/applications/applications.js";
import { ReactSVG } from "react-svg"; import { ReactSVG } from "react-svg";
import { closeTab } from "../../features/utils/browser.js"; import { closeTab } from "../../features/utils/browser.js";
import { useKeyboardListener } from "../../hooks/utils/keyboard.js"; import { useKeyboardListener } from "../../hooks/utils/keyboard.js";
import { useVirtualRoot } from "../../hooks/virtual-drive/VirtualRootContext.js";
export function HomeMenu({ active, setActive }) { export function HomeMenu({ active, setActive }) {
const windowsManager = useWindowsManager(); const windowsManager = useWindowsManager();
const virtualRoot = useVirtualRoot();
const classNames = [styles["Container-outer"]]; const classNames = [styles["Container-outer"]];
if (active) if (active)
@ -46,7 +48,13 @@ export function HomeMenu({ active, setActive }) {
<button title="Settings"> <button title="Settings">
<FontAwesomeIcon icon={faGear}/> <FontAwesomeIcon icon={faGear}/>
</button> </button>
<button title="Info"> <button title="Info" onClick={() => {
setActive(false);
windowsManager.open("text-editor", {
mode: "view",
file: virtualRoot.navigate("~/Documents").findFile("info", "md")
});
}}>
<FontAwesomeIcon icon={faCircleInfo}/> <FontAwesomeIcon icon={faCircleInfo}/>
</button> </button>
<button title="Images" onClick={() => { <button title="Images" onClick={() => {

View file

@ -50,6 +50,7 @@ export default class ApplicationsManager {
app = this.getApplication("media-viewer"); app = this.getApplication("media-viewer");
break; break;
case "txt": case "txt":
case "md":
app = this.getApplication("text-editor"); app = this.getApplication("text-editor");
break; break;
} }