Added markdown preview for text editor
This commit is contained in:
parent
73083693bb
commit
a26e3a3809
8 changed files with 102 additions and 16 deletions
12
package-lock.json
generated
12
package-lock.json
generated
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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
13
public/documents/info.md
Normal 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).
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
@ -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={() => {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue