Permet de charger en local 1 ou plusieurs pages

Fix #1
Fix #2
This commit is contained in:
2022-03-17 12:54:49 +01:00
parent d52afe549d
commit e1f2294fe4
10 changed files with 414 additions and 550 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);