@@ -12,17 +12,14 @@ import {
|
||||
withRouter,
|
||||
useHistory
|
||||
} from "react-router-dom";
|
||||
|
||||
interface List {
|
||||
id: string;
|
||||
date: string;
|
||||
}
|
||||
import {IList} from "../../interfaces/IList";
|
||||
import {isAllLoadedLocally} from "../../utils";
|
||||
|
||||
const app = document.getElementById('app');
|
||||
const word = "shikiryu"; // FIXME should be in db and ≠ between users
|
||||
|
||||
const sessionPassphrase = sessionStorage.getItem("key");
|
||||
let pages: List[] = [];
|
||||
const sessionPassphrase = sessionStorage.getItem("key") as string;
|
||||
let pages: IList[] = [];
|
||||
let getPageContentUrl,
|
||||
postUrl,
|
||||
removeUrl,
|
||||
@@ -31,7 +28,7 @@ let getPageContentUrl,
|
||||
|
||||
if (app) {
|
||||
getPageContentUrl = "" + app.getAttribute('data-url');
|
||||
pages = JSON.parse("" + app.getAttribute('data-list'));
|
||||
pages = JSON.parse("" + app.getAttribute('data-list')) as IList[];
|
||||
postUrl = "" + app.getAttribute('data-post');
|
||||
removeUrl = "" + app.getAttribute('data-remove');
|
||||
csrf = "" + app.getAttribute('data-csrf');
|
||||
@@ -39,11 +36,14 @@ if (app) {
|
||||
}
|
||||
|
||||
function App() {
|
||||
const [listPages, setListPages] = React.useState(pages);
|
||||
const [passphrase, setPassphrase] = React.useState(sessionPassphrase);
|
||||
const [listPages, setListPages] = React.useState<IList[]>(pages);
|
||||
const [passphrase, setPassphrase] = React.useState<string>(sessionPassphrase);
|
||||
const history = useHistory();
|
||||
const [user, setUser] = React.useState(checkPassphrase());
|
||||
const [error, isError] = React.useState(!checkPassphrase());
|
||||
const [allLoaded, setAllLoaded] = React.useState<boolean>(isAllLoadedLocally(pages));
|
||||
|
||||
const loadPages = React.useRef(() => {return ;});
|
||||
|
||||
const signin = cb => {
|
||||
const isAuthenticated = checkPassphrase();
|
||||
@@ -92,6 +92,14 @@ function App() {
|
||||
return (<div></div>);
|
||||
}
|
||||
|
||||
const LoadButton = function() {
|
||||
return allLoaded ? (<></>) : (
|
||||
<Button variant="outlined" size="small" sx={{align: "right"}} onClick={() => loadPages.current()}>
|
||||
Charger les pages localement
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
const AuthButton = function() {
|
||||
const history = useHistory();
|
||||
|
||||
@@ -156,9 +164,11 @@ function App() {
|
||||
pages={listPages}
|
||||
url={getPageContentUrl}
|
||||
passphrase={passphrase}
|
||||
setPassphrase={setPassphrase}
|
||||
csrf={csrf}
|
||||
removeUrl={removeUrl}/>
|
||||
removeUrl={removeUrl}
|
||||
loadPages={loadPages}
|
||||
setAllLoaded={setAllLoaded}
|
||||
/>
|
||||
<Divider/>
|
||||
<PageForm setListPages={setListPages} csrf={csrf} url={postUrl} passphrase={passphrase}/>
|
||||
</div>
|
||||
@@ -168,6 +178,7 @@ function App() {
|
||||
return (
|
||||
<div>
|
||||
<AuthButton />
|
||||
<LoadButton />
|
||||
<Error/>
|
||||
<Divider sx={{height: "20px", mb: "20px", mt:"10px"}}/>
|
||||
<Switch>
|
||||
|
@@ -1,13 +1,11 @@
|
||||
import {Grid, Pagination} from '@mui/material';
|
||||
import * as React from 'react';
|
||||
import Page from "./Page";
|
||||
import {IPages} from "../../interfaces/IPages";
|
||||
import {IList} from "../../interfaces/IList";
|
||||
import {isAllLoadedLocally} from "../../utils";
|
||||
|
||||
interface List {
|
||||
id: string;
|
||||
date: string;
|
||||
}
|
||||
|
||||
export default function Pages({pages, url, removeUrl, csrf, passphrase, setPassphrase}) {
|
||||
export default function Pages({pages, url, removeUrl, csrf, passphrase, loadPages, setAllLoaded}: IPages) {
|
||||
const isPassphraseSet = passphrase !== null;
|
||||
const perPage = 3;
|
||||
const total = pages.length;
|
||||
@@ -35,20 +33,42 @@ export default function Pages({pages, url, removeUrl, csrf, passphrase, setPassp
|
||||
console.log(newListPages.length);
|
||||
console.log(listPages.length);
|
||||
updateListPages(newListPages.slice((currentPage - 1) * perPage, ((currentPage - 1) * perPage) + perPage).map(page =>
|
||||
<Page page={page} url={url} remove={removePage} setPassphrase={setPassphrase} passphrase={passphrase}
|
||||
key={page.id}/>));
|
||||
<Page page={page} url={url} remove={removePage} passphrase={passphrase} key={page.id}/>));
|
||||
}
|
||||
}
|
||||
|
||||
const [listPagesDisplayed, updateListPages] = React.useState(listPages.slice((currentPage-1) * perPage, ((currentPage-1) * perPage) + perPage ).map(page =>
|
||||
<Page page={page} url={url} remove={removePage} setPassphrase={setPassphrase} passphrase={passphrase} key={page.id}/>));
|
||||
<Page page={page} url={url} remove={removePage} passphrase={passphrase} key={page.id} />));
|
||||
|
||||
const handlePageChange = (event: React.ChangeEvent<unknown>, value: number) => {
|
||||
setPage(value);
|
||||
updateListPages(listPages.slice((value-1) * perPage, ((value-1) * perPage) + perPage).map(page =>
|
||||
<Page page={page} url={url} remove={removePage} setPassphrase={setPassphrase} passphrase={passphrase} key={page.id}/>));
|
||||
<Page page={page} url={url} remove={removePage} passphrase={passphrase} key={page.id}/>));
|
||||
};
|
||||
|
||||
const loadAllPages = function() {
|
||||
listPages.map(function(page) {
|
||||
loadPage(page).then(r => {setAllLoaded(isAllLoadedLocally(listPages));});
|
||||
});
|
||||
}
|
||||
|
||||
async function loadPage(page: IList) {
|
||||
if (localStorage.getItem(page.id + "text") === null) {
|
||||
const response = await fetch(url.replace("replace_me", page.id));
|
||||
|
||||
const json = await response.json();
|
||||
localStorage.setItem(page.id + "title", json.metadata.title);
|
||||
localStorage.setItem(page.id + "text", json.content);
|
||||
updateListPages([]);
|
||||
updateListPages(listPages.slice((currentPage-1) * perPage, ((currentPage-1) * perPage) + perPage).map(page =>
|
||||
<Page page={page} url={url} remove={removePage} passphrase={passphrase} key={page.id}/>));
|
||||
}
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
loadPages.current = loadAllPages;
|
||||
}, [])
|
||||
|
||||
if (isPassphraseSet) {
|
||||
return (
|
||||
<div>
|
||||
|
@@ -1,56 +1,26 @@
|
||||
import {
|
||||
Alert,
|
||||
AlertTitle,
|
||||
Card, CardActions,
|
||||
CardContent,
|
||||
Card, CardContent,
|
||||
CardHeader, Collapse,
|
||||
Grid, IconButton, IconButtonProps,
|
||||
Typography
|
||||
Grid, IconButton
|
||||
} 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 {Delete, Download} from "@mui/icons-material";
|
||||
import MDEditor from "@uiw/react-md-editor";
|
||||
import {PropPage} from "../../interfaces/PropPage";
|
||||
|
||||
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, passphrase, setPassphrase, remove}) {
|
||||
const [more, setMore] = React.useState(false);
|
||||
const [expanded, setExpanded] = React.useState(false);
|
||||
const Page = function({page, url, passphrase, remove}: PropPage) {
|
||||
const [more, setMore] = React.useState<boolean>(false);
|
||||
const handleMoreClick = () => { setMore(!more); };
|
||||
const handleExpandClick = () => { onLoad().then(r => setExpanded(!expanded)); };
|
||||
|
||||
const 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 () => {
|
||||
const [title, setTitle] = React.useState<string>(encryptStorage.decrypt(page.id + "title"));
|
||||
const [content, setContent] = React.useState<string>(encryptStorage.decrypt(page.id + "text"));
|
||||
const onLoad = async () => {
|
||||
if (localStorage.getItem(page.id + "text") === null) {
|
||||
const response = await fetch(url.replace("replace_me", page.id));
|
||||
|
||||
@@ -59,54 +29,59 @@ export default function Page({page, url, passphrase, setPassphrase, remove}) {
|
||||
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>
|
||||
}
|
||||
let icons;
|
||||
if (content === null || content === "") {
|
||||
icons = (<div>
|
||||
<IconButton aria-label="load" onClick={onLoad} >
|
||||
<Download />
|
||||
</IconButton>
|
||||
<IconButton aria-label="settings" onClick={handleMoreClick}>
|
||||
<MoreVertIcon />
|
||||
</IconButton>
|
||||
</div>);
|
||||
} else {
|
||||
icons = (
|
||||
<IconButton aria-label="settings" onClick={handleMoreClick}>
|
||||
<MoreVertIcon />
|
||||
</IconButton>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
return (
|
||||
<Grid item xs={12} sm={12} md={12} id={page.id}>
|
||||
{alert_popup}
|
||||
<Card>
|
||||
<CardHeader
|
||||
action={
|
||||
<IconButton aria-label="settings" onClick={handleMoreClick}>
|
||||
<MoreVertIcon />
|
||||
</IconButton>
|
||||
icons
|
||||
}
|
||||
title={title}
|
||||
subheader={page.date}
|
||||
/>
|
||||
<Collapse in={more} timeout="auto" unmountOnExit>
|
||||
<IconButton aria-label="remove" onClick={() => {remove(page.id)}} >
|
||||
<Delete />
|
||||
</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>
|
||||
<MDEditor.Markdown source={content} />
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Collapse>
|
||||
>
|
||||
<Collapse in={more} timeout="auto" unmountOnExit>
|
||||
<IconButton aria-label="remove" onClick={() => {remove(page.id)}} >
|
||||
<Delete />
|
||||
</IconButton>
|
||||
</Collapse>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<MDEditor.Markdown source={content} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
);
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return (
|
||||
<Grid item xs={12} sm={12} md={12} id={page.id}>
|
||||
<Alert severity="error">
|
||||
<AlertTitle>Erreur</AlertTitle>
|
||||
Cette page ne peut pas être décodée — <strong>Réindiquez votre clef!</strong>
|
||||
</Alert>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default React.forwardRef(Page);
|
||||
|
Reference in New Issue
Block a user