improv tos page
This commit is contained in:
parent
03fc697dd9
commit
9187eb0253
|
@ -0,0 +1,11 @@
|
|||
import { AppBar, Typography } from "@mui/material";
|
||||
|
||||
export default function Header() {
|
||||
return (
|
||||
<AppBar position="sticky" color="transparent">
|
||||
<Typography component="h1" variant="h4">
|
||||
Socialvoid
|
||||
</Typography>
|
||||
</AppBar>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardContent,
|
||||
Button,
|
||||
Typography,
|
||||
CardActions,
|
||||
} from "@mui/material";
|
||||
import {
|
||||
Star,
|
||||
StarOutline,
|
||||
ChangeCircle,
|
||||
ChangeCircleOutlined,
|
||||
} from "@mui/icons-material";
|
||||
import { Post as PostType } from "socialvoid";
|
||||
import { useState } from "react";
|
||||
import { unparse } from "../utils";
|
||||
|
||||
export default function Post({ post }: { post: PostType }) {
|
||||
const [liked, setLiked] = useState(post.flags.includes("liked"));
|
||||
const [reposted, setReposted] = useState(post.flags.includes("reposted"));
|
||||
|
||||
const like = async () => {
|
||||
if (liked) {
|
||||
setLiked(false);
|
||||
} else {
|
||||
setLiked(true);
|
||||
}
|
||||
};
|
||||
|
||||
const repost = async () => {
|
||||
console.log(reposted);
|
||||
if (reposted) {
|
||||
setReposted(false);
|
||||
} else {
|
||||
setReposted(true);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader
|
||||
title={post.peer?.name}
|
||||
subheader={"@" + post.peer?.username}
|
||||
/>
|
||||
<CardContent>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: unparse(post.text!, post.entities!),
|
||||
}}
|
||||
></Typography>
|
||||
</CardContent>
|
||||
<CardActions
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-evenly",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
color="success"
|
||||
startIcon={liked ? <Star /> : <StarOutline />}
|
||||
onClick={like}
|
||||
>
|
||||
{" "}
|
||||
{post.like_count}
|
||||
</Button>
|
||||
<Button
|
||||
color="info"
|
||||
startIcon={reposted ? <ChangeCircle /> : <ChangeCircleOutlined />}
|
||||
onClick={repost}
|
||||
>
|
||||
{" "}
|
||||
{post.like_count}
|
||||
</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
);
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
import Typography from "@mui/material/Typography";
|
||||
import Link from "@mui/material/Link";
|
||||
|
||||
export default function Copyright(props: any) {
|
||||
return (
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
align="center"
|
||||
{...props}
|
||||
>
|
||||
{"Copyright © "}
|
||||
<Link color="inherit" href="https://rojser.best">
|
||||
Socialvoid Next
|
||||
</Link>{" "}
|
||||
{new Date().getFullYear()}
|
||||
{"."}
|
||||
</Typography>
|
||||
);
|
||||
}
|
|
@ -20,7 +20,7 @@
|
|||
"pullstate": "^1.23.0",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"socialvoid": "0.0.0-alpha.3"
|
||||
"socialvoid": "^0.0.0-alpha.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/nprogress": "^0.2.0",
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
import { useMemo } from "react";
|
||||
import { AppProps } from "next/app";
|
||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||
import CssBaseline from "@mui/material/CssBaseline";
|
||||
import Container from "@mui/material/Container";
|
||||
import { createTheme, ThemeProvider } from "@mui/material/styles";
|
||||
import {
|
||||
createTheme,
|
||||
ThemeProvider,
|
||||
useMediaQuery,
|
||||
CssBaseline,
|
||||
Container,
|
||||
} from "@mui/material";
|
||||
import { SnackbarProvider } from "notistack";
|
||||
import Header from "../components/Header";
|
||||
|
||||
function App({ Component, pageProps }: AppProps) {
|
||||
const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)");
|
||||
|
@ -23,7 +27,7 @@ function App({ Component, pageProps }: AppProps) {
|
|||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
<SnackbarProvider>
|
||||
<Container component="main" maxWidth="md" sx={{ mt: 3 }}>
|
||||
<Container component="main" maxWidth="sm" sx={{ mt: 3 }}>
|
||||
<Component {...pageProps} />
|
||||
</Container>
|
||||
</SnackbarProvider>
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
export default function Test() {
|
||||
const a = "Hello".repeat(10);
|
||||
return (
|
||||
<>
|
||||
<p>{a}</p>
|
||||
<p>{a}</p>
|
||||
<p>{a}</p>
|
||||
<p>{a}</p>
|
||||
<p>{a}</p>
|
||||
<p>{a}</p>
|
||||
<p>{a}</p>
|
||||
<p>{a}</p>
|
||||
<p>{a}</p>
|
||||
<p>{a}</p>
|
||||
<p>{a}</p>
|
||||
<p>{a}</p>
|
||||
<p>{a}</p>
|
||||
<p>{a}</p>
|
||||
<p>{a}</p>
|
||||
<p>{a}</p>
|
||||
<p>{a}</p>
|
||||
<p>{a}</p>
|
||||
<p>{a}</p>
|
||||
<p>{a}</p>
|
||||
<p>{a}</p>
|
||||
<p>{a}</p>
|
||||
<p>{a}</p>
|
||||
<p>{a}</p>
|
||||
<p>{a}</p>
|
||||
<p>{a}</p>
|
||||
<p>{a}</p>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import { useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useSnackbar } from "notistack";
|
||||
import Post from "../components/Post";
|
||||
import { dispatch } from "../utils";
|
||||
import { Post as PostType } from "socialvoid";
|
||||
|
||||
export default function Timeline() {
|
||||
const router = useRouter();
|
||||
const snackbar = useSnackbar();
|
||||
const [posts, setPosts] = useState<PostType[]>();
|
||||
|
||||
dispatch(
|
||||
async (client) => {
|
||||
const posts = await client.timeline.retrieveFeed();
|
||||
setPosts(posts);
|
||||
},
|
||||
router,
|
||||
snackbar,
|
||||
{
|
||||
requireToBeAuthenticated: true,
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{posts?.map((post) => (
|
||||
<Post post={post} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -6,14 +6,18 @@ import Box from "@mui/material/Box";
|
|||
import Typography from "@mui/material/Typography";
|
||||
import FormControlLabel from "@mui/material/FormControlLabel";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { pushWithQuery, dispatch } from "../utils";
|
||||
import { pushWithQuery, dispatch, unparse } from "../utils";
|
||||
import { HelpDocument } from "socialvoid";
|
||||
|
||||
export default function TOS() {
|
||||
const router = useRouter();
|
||||
const snackbar = useSnackbar();
|
||||
|
||||
const [tosId, setTosId] = useState("");
|
||||
const [text, setText] = useState("Loading...");
|
||||
const [document, setDocument] = useState<HelpDocument>({
|
||||
id: "",
|
||||
text: "Loading...",
|
||||
entities: [],
|
||||
});
|
||||
const [disabled, setDisabled] = useState(true);
|
||||
|
||||
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
|
@ -34,9 +38,8 @@ export default function TOS() {
|
|||
|
||||
dispatch(
|
||||
async (client) => {
|
||||
const { id, text } = await client.help.getTermsOfService();
|
||||
setTosId(id);
|
||||
setText(text);
|
||||
const document = await client.help.getTermsOfService();
|
||||
setDocument(document);
|
||||
setDisabled(false);
|
||||
},
|
||||
router,
|
||||
|
@ -46,12 +49,14 @@ export default function TOS() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<Typography component="h1" variant="h5" hidden={disabled}>
|
||||
Socialvoid - Terms of Service
|
||||
</Typography>
|
||||
<p>{text}</p>
|
||||
<Typography
|
||||
variant="body1"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: unparse(document.text, document.entities),
|
||||
}}
|
||||
></Typography>
|
||||
<Box component="form" noValidate sx={{ mt: 3 }} onSubmit={handleSubmit}>
|
||||
<input type="hidden" name="tosId" value={tosId} />
|
||||
<input type="hidden" name="tosId" value={document.id} />
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
|
|
|
@ -3,3 +3,4 @@ export * from "./dispatch";
|
|||
export * from "./router";
|
||||
export * from "./params";
|
||||
export * from "./validation";
|
||||
export * from "./parser";
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
import { TextEntity } from "socialvoid";
|
||||
|
||||
function is(x: TextEntity, y: TextEntity) {
|
||||
return (
|
||||
x.offset == y.offset &&
|
||||
x.length == y.length &&
|
||||
x.type == y.type &&
|
||||
x.value == y.value
|
||||
);
|
||||
}
|
||||
|
||||
function htmlEscape(s: string) {
|
||||
return s
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
function _unparse(
|
||||
text: string,
|
||||
entities: TextEntity[],
|
||||
offset: number,
|
||||
length: number
|
||||
) {
|
||||
let noffset = offset;
|
||||
let ntext = "";
|
||||
|
||||
for (const entity of entities) {
|
||||
if (offset >= entity.length + entity.offset) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ntext += htmlEscape(text.slice(offset, entity.offset));
|
||||
|
||||
const netntities = entities.filter(
|
||||
(entity2) =>
|
||||
entity.offset + entity.length >= entity2.offset && !is(entity, entity2)
|
||||
);
|
||||
|
||||
let atext = "";
|
||||
|
||||
if (netntities) {
|
||||
atext = _unparse(text, netntities, entity.offset, entity.length);
|
||||
} else {
|
||||
atext = htmlEscape(
|
||||
text.slice(entity.offset, entity.offset + entity.length)
|
||||
);
|
||||
}
|
||||
|
||||
switch (entity.type) {
|
||||
case "BOLD":
|
||||
ntext += `<b>${atext}</b>`;
|
||||
break;
|
||||
case "ITALIC":
|
||||
ntext += `<i>${atext}</i>`;
|
||||
break;
|
||||
case "CODE":
|
||||
ntext += `<code>${atext}</code>`;
|
||||
break;
|
||||
case "STRIKE":
|
||||
ntext += `<s>${atext}</s>`;
|
||||
break;
|
||||
case "UNDERLINE":
|
||||
ntext += `<u>${atext}</u>`;
|
||||
break;
|
||||
case "URL":
|
||||
ntext += `<a href="${htmlEscape(entity.value!)}">${atext}</a>`;
|
||||
break;
|
||||
case "MENTION":
|
||||
ntext += `<a href="sv://peer/${encodeURIComponent(
|
||||
entity.value!
|
||||
)}">${atext}</a>`;
|
||||
break;
|
||||
case "HASHTAG":
|
||||
ntext += atext;
|
||||
}
|
||||
|
||||
offset = entity.offset + entity.length;
|
||||
}
|
||||
|
||||
return ntext + htmlEscape(text.slice(offset, length + noffset));
|
||||
}
|
||||
|
||||
export function unparse(text: string, entities: TextEntity[]) {
|
||||
entities = entities.sort((a) => a.offset - a.length);
|
||||
|
||||
return _unparse(text, entities, 0, text.length).replace(/\n/g, "<br />");
|
||||
}
|
|
@ -3215,10 +3215,10 @@ slash@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
|
||||
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
|
||||
|
||||
socialvoid@0.0.0-alpha.3:
|
||||
version "0.0.0-alpha.3"
|
||||
resolved "https://registry.yarnpkg.com/socialvoid/-/socialvoid-0.0.0-alpha.3.tgz#84b5bafc1ca93fc23411801deba60b350e138a10"
|
||||
integrity sha512-Ljznz3Yp+1Sf3hRQnMzNhVevyhPz9PUi13h/SNNRqgu2ttAnE7ee+PQJjGaArYvVjfTScWezLz36tPw5O82paQ==
|
||||
socialvoid@^0.0.0-alpha.7:
|
||||
version "0.0.0-alpha.7"
|
||||
resolved "https://registry.yarnpkg.com/socialvoid/-/socialvoid-0.0.0-alpha.7.tgz#524d93ca5fa6d4d422118c4f7bf971ff71cc5ad5"
|
||||
integrity sha512-5S2d2GCBqO2IWSL4RA0eC2UDpsKYwWFQANcLL0jDodQUakfG2DJ3e5hV7FWW59fTRqYpQ9ykNOgjJWYmQlB8sQ==
|
||||
dependencies:
|
||||
form-data "^4.0.0"
|
||||
node-fetch "^2.6.5"
|
||||
|
|
Loading…
Reference in New Issue