Hello world

This commit is contained in:
2022-02-11 17:26:15 +01:00
commit e889bb0977
138 changed files with 147884 additions and 0 deletions

View File

@@ -0,0 +1,59 @@
import ReactDOM from 'react-dom';
import PageForm from "./Form";
import Pages from "./List";
import Prompt from "./Prompt";
import {useState} from "react";
import * as React from 'react';
import {Divider, Paper} from "@mui/material";
interface List {
id: string;
date: string;
}
const app = document.getElementById('app');
let sessionPassphrase = sessionStorage.getItem("key");
let pages: List[] = [];
let getPageContentUrl = "";
let postUrl = "";
let removeUrl = "";
let csrf = "";
if (app) {
getPageContentUrl = "" + app.getAttribute('data-url');
pages = JSON.parse("" + app.getAttribute('data-list'));
postUrl = "" + app.getAttribute('data-post');
removeUrl = "" + app.getAttribute('data-remove');
csrf = "" + app.getAttribute('data-csrf');
ReactDOM.render(<App/>, app);
}
export default function App() {
const [listPages, setListPages] = useState(pages);
const [passphrase, setPassphrase] = useState(sessionPassphrase);
return (
<div>
<div className="container">
<div className="row justify-content-center">
<div className="col-md-8">
{/*<div className="card">*/}
{/*<Paper elevation={3}>*/}
<Prompt open={passphrase === null} setOpen={setPassphrase}/>
<Pages
pages={listPages}
url={getPageContentUrl}
passphrase={passphrase}
setPassphrase={setPassphrase}
csrf={csrf}
removeUrl={removeUrl}/>
<Divider/>
<PageForm setListPages={setListPages} csrf={csrf} url={postUrl} passphrase={passphrase}/>
{/*</Paper>*/}
{/*</div>*/}
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,110 @@
import * as React from 'react';
import {EncryptStorage} from 'storage-encryption';
import {Button, Stack, TextField} from "@mui/material";
import MDEditor from '@uiw/react-md-editor';
let encryptStorage = new EncryptStorage('test'); // TODO la clef doit venir de l'utilisateur
export default function PageForm({setListPages, csrf, url, passphrase}) {
const isPassphraseSet = passphrase !== null;
const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
encryptStorage = new EncryptStorage(passphrase);
let HTMLForm : HTMLFormElement = event.currentTarget;
let decryptedFormData = new FormData(HTMLForm);
let encryptedFormData = new FormData();
for (let [key, value] of decryptedFormData.entries()) {
encryptStorage.encrypt('uuid'+key, value);
let newEncryptedString = localStorage.getItem('uuid'+key);
if (newEncryptedString) {
encryptedFormData.append(key, newEncryptedString);
}
}
encryptedFormData.append('_token', csrf);
let response = await fetch(url, {
method: 'POST',
body: encryptedFormData
});
const json = await response.json();
const uuid = json.uuid;
for (let key of decryptedFormData.keys()) {
let newEncryptedString = localStorage.getItem('uuid'+key);
localStorage.setItem(uuid+key, ""+newEncryptedString);
localStorage.removeItem("uuid"+key);
}
if (json.success) {
HTMLForm.reset();
setListPages(previousList => [
...previousList,
{
id: uuid,
date: json.date,
title: decryptedFormData.get("title"),
content: decryptedFormData.get("text"),
}]);
}
}
if (isPassphraseSet) {
return (
/* <Stack
component="form"
sx={{
width: '25ch',
}}
spacing={2}
noValidate
autoComplete="off"
>
<TextField
label="Titre"
id="title"
name="title"
variant="outlined"
size="small"
/>
<MDEditor
value={value}
onChange={setValue}
/>
<MDEditor.Markdown source={value} />
<TextField
label="Texte"
name="content"
id="filled-hidden-label-normal"
defaultValue="Normal"
variant="outlined"
multiline
maxRows={15}
/>
<Button variant="contained" component="span" onClick={onSubmit}>
Enregistrer
</Button>
</Stack>*/
<div className="container">
<div className="row justify-content-center">
<div className="col-md-8">
<div className="card">
<form action={url} id="postPage" method="post" onSubmit={onSubmit}>
<label htmlFor="title">Titre:</label>
<input id="title" name="title"/>
<hr/>
<label htmlFor="text">Texte:</label>
<textarea id="text" name="text"/>
<hr/>
<input type="submit" value="Enregistrer"/>
</form>
</div>
</div>
</div>
</div>
);
}
return (<div></div>);
}

View File

@@ -0,0 +1,29 @@
import {Grid, List, Pagination} from '@mui/material';
import * as React from 'react';
import Page from "./Page";
interface List {
id: string;
date: string;
}
export default function Pages({pages, url, removeUrl, csrf, passphrase, setPassphrase}) {
const isPassphraseSet = passphrase !== null;
let listPages = pages.map(page =>
<Page page={page} url={url} setPassphrase={setPassphrase} passphrase={passphrase} csrf={csrf} removeUrl={removeUrl} />
)
if (isPassphraseSet) {
return (
<div>
<Grid container rowSpacing={1} columnSpacing={1}>
{listPages}
</Grid>
<Pagination count={10} color="primary" />
</div>
);
}
return (<div></div>);
}

View File

@@ -0,0 +1,123 @@
import {
Alert,
AlertTitle,
Card, CardActions,
CardContent,
CardHeader, Collapse,
Grid, IconButton, IconButtonProps,
Typography
} from '@mui/material';
import MoreVertIcon from '@mui/icons-material/MoreVert';
import { styled } from '@mui/material/styles';
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import * as React from 'react';
import {EncryptStorage} from 'storage-encryption';
import {Delete} from "@mui/icons-material";
import {unmountComponentAtNode} from "react-dom";
interface Page {
date: string;
title: string;
content: string;
}
interface ExpandMoreProps extends IconButtonProps {
expand: boolean;
}
const ExpandMore = styled((props: ExpandMoreProps) => {
const { expand, ...other } = props;
return <IconButton {...other} />;
})(({ theme, expand }) => ({
transform: !expand ? 'rotate(0deg)' : 'rotate(180deg)',
marginLeft: 'auto',
transition: theme.transitions.create('transform', {
duration: theme.transitions.duration.shortest,
}),
}));
export default function Page({page, url, removeUrl, csrf, passphrase, setPassphrase}) {
const [more, setMore] = React.useState(false);
const [expanded, setExpanded] = React.useState(false);
const handleMoreClick = () => { setMore(!more); };
const handleExpandClick = () => { onLoad().then(r => setExpanded(!expanded)); };
const remove = async () => {
const formData = new FormData();
formData.set('_token', csrf);
formData.set('_method', 'DELETE');
let response = await fetch(removeUrl.replace("replace_me", page.id), {
method: 'POST', body: formData
});
const json = await response.json();
unmountComponentAtNode(document.getElementById(page.id));
};
let encryptStorage = new EncryptStorage(passphrase);
let title, content = "";
let alert_popup:JSX.Element|null = null;
let setTitle, setContent: React.Dispatch<any>|null = null;
let onLoad = async () => {};
try {
[content, setContent] = React.useState(encryptStorage.decrypt(page.id + "text"));
[title, setTitle] = React.useState(encryptStorage.decrypt(page.id + "title"));
onLoad = async () => {
if (localStorage.getItem(page.id + "text") === null) {
let response = await fetch(url.replace("replace_me", page.id));
let json = await response.json();
localStorage.setItem(page.id + "title", json.metadata.title);
localStorage.setItem(page.id + "text", json.content);
}
setTitle(encryptStorage.decrypt(page.id + "title"));
// @ts-ignore
setContent(encryptStorage.decrypt(page.id + "text"));
}
} catch (e) {
console.error(e);
setPassphrase(null);
alert_popup = <Alert severity="error">
<AlertTitle>Erreur</AlertTitle>
Vos pages ne peuvent pas être décodées <strong>Réindiquez votre clef!</strong>
</Alert>
}
return (
<Grid item xs={12} sm={6} md={6} id={page.id}>
{alert_popup}
<Card>
<CardHeader
action={
<IconButton aria-label="settings">
<MoreVertIcon onClick={handleMoreClick} />
</IconButton>
}
title={title}
subheader={page.date}
/>
<Collapse in={more} timeout="auto" unmountOnExit>
<IconButton aria-label="remove">
<Delete onClick={remove} />
</IconButton>
</Collapse>
<CardActions disableSpacing>
<ExpandMore
expand={expanded}
onClick={handleExpandClick}
aria-expanded={expanded}
aria-label="show more"
>
<ExpandMoreIcon />
</ExpandMore>
</CardActions>
<Collapse in={expanded} timeout="auto" unmountOnExit>
<CardContent>
<Typography paragraph>
{content}
</Typography>
</CardContent>
</Collapse>
</Card>
</Grid>
);
}

View File

@@ -0,0 +1,43 @@
import {Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, TextField} from "@mui/material";
import * as React from 'react';
export default function Prompt({open, setOpen}) {
const handleEnterClose = (e) => {
if (e.key === 'Enter') {
handleClose();
}
}
const handleClose = () => {
// @ts-ignore
const passphrase = ""+document.getElementById("passphrase").value;
sessionStorage.setItem("key", passphrase);
setOpen(passphrase);
};
return (
<div>
<Dialog open={open} onClose={handleClose}>
<DialogTitle>Hey ! Si c'est bien toi, donne moi la clef !</DialogTitle>
<DialogContent>
<DialogContentText>
Avant de pouvoir lire ou écrire, il faut ouvrir ton carnet avec la clef.
</DialogContentText>
<TextField
autoFocus
margin="dense"
id="passphrase"
label="Passphrase"
type="password"
fullWidth
variant="standard"
onKeyPress={handleEnterClose}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Tourner la clef</Button>
</DialogActions>
</Dialog>
</div>
);
};