mirror of
https://github.com/aljazceru/landscape-template.git
synced 2026-02-23 07:24:32 +01:00
fix: fix multline breaks in post body, add slugs to urls
This commit is contained in:
@@ -83,7 +83,7 @@ function App() {
|
||||
<Route path="/products/category/:id" element={<CategoryPage />} />
|
||||
<Route path="/products" element={<ExplorePage />} />
|
||||
|
||||
<Route path="/blog/post/:type/:id" element={<PostDetailsPage />} />
|
||||
<Route path="/blog/post/:type/:id/*" element={<PostDetailsPage />} />
|
||||
<Route path="/blog/preview-post/:type" element={<PreviewPostPage />} />
|
||||
<Route path="/blog/create-post" element={<ProtectedRoute><CreatePostPage /></ProtectedRoute>} />
|
||||
<Route path="/blog" element={<FeedPage />} />
|
||||
@@ -92,7 +92,7 @@ function App() {
|
||||
|
||||
<Route path="/donate" element={<DonatePage />} />
|
||||
|
||||
<Route path="/profile/:id" element={<ProfilePage />} />
|
||||
<Route path="/profile/:id/*" element={<ProfilePage />} />
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
<Route path="/logout" element={<LogoutPage />} />
|
||||
|
||||
|
||||
@@ -19,7 +19,8 @@ export default function SaveModule(props: Props) {
|
||||
const changeCallback = useDebouncedCallback(ctx => {
|
||||
|
||||
const { state } = ctx;
|
||||
const md = getMarkdown(state);
|
||||
let md = getMarkdown(state);
|
||||
md = md.replace(/\n(?=\n)/g, "\n\n<br/>\n");
|
||||
onChange(md);
|
||||
}, [], 500)
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
import '@szhsin/react-menu/dist/index.css';
|
||||
import { FiChevronDown, FiLogIn } from "react-icons/fi";
|
||||
import Avatar from "src/features/Profiles/Components/Avatar/Avatar";
|
||||
import { createRoute } from "src/utils/routing";
|
||||
|
||||
|
||||
export default function NavDesktop() {
|
||||
@@ -171,10 +172,10 @@ export default function NavDesktop() {
|
||||
menuClassName='!p-8 !rounded-12'
|
||||
menuButton={<MenuButton ><Avatar src={curUser.avatar} width={40} /> </MenuButton>}>
|
||||
<MenuItem
|
||||
href={`/profile/${curUser.id}`}
|
||||
href={createRoute({ type: 'profile', id: curUser.id, username: curUser.name })}
|
||||
onClick={(e) => {
|
||||
e.syntheticEvent.preventDefault();
|
||||
navigate(`/profile/${curUser.id}`);
|
||||
navigate(createRoute({ type: 'profile', id: curUser.id, username: curUser.name }));
|
||||
}}
|
||||
className='!p-16 font-medium flex gap-16 hover:bg-gray-100 !rounded-12'
|
||||
>
|
||||
|
||||
@@ -14,6 +14,7 @@ import styles from './styles.module.css'
|
||||
import '@szhsin/react-menu/dist/index.css';
|
||||
import { Menu, MenuButton, MenuItem } from "@szhsin/react-menu";
|
||||
import Avatar from "src/features/Profiles/Components/Avatar/Avatar";
|
||||
import { createRoute } from "src/utils/routing";
|
||||
|
||||
|
||||
|
||||
@@ -85,10 +86,10 @@ export default function NavMobile() {
|
||||
menuClassName='!p-8 !rounded-12'
|
||||
menuButton={<MenuButton ><Avatar src={curUser.avatar} width={32} /> </MenuButton>}>
|
||||
<MenuItem
|
||||
href={`/profile/${curUser.id}`}
|
||||
href={createRoute({ type: 'profile', id: curUser.id, username: curUser.name })}
|
||||
onClick={(e) => {
|
||||
e.syntheticEvent.preventDefault();
|
||||
navigate(`/profile/${curUser.id}`);
|
||||
navigate(createRoute({ type: 'profile', id: curUser.id, username: curUser.name }));
|
||||
}}
|
||||
className='!p-16 font-medium flex gap-16 hover:bg-gray-100 !rounded-12'
|
||||
>
|
||||
|
||||
@@ -3,6 +3,7 @@ import dayjs from 'dayjs'
|
||||
import { UnionToObjectKeys } from 'src/utils/types/utils';
|
||||
import { trimText } from 'src/utils/helperFunctions';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { createRoute } from 'src/utils/routing';
|
||||
|
||||
interface Props {
|
||||
author: {
|
||||
@@ -44,7 +45,7 @@ export default function Header({
|
||||
|
||||
return (
|
||||
<div className='flex gap-8'>
|
||||
<Link to={`/profile/${props.author.id}`}>
|
||||
<Link to={createRoute({ type: 'profile', id: props.author.id, username: props.author.name })}>
|
||||
<Avatar width={avatarSize[size]} src={props.author.avatar} />
|
||||
</Link>
|
||||
<div className='overflow-hidden'>
|
||||
|
||||
@@ -6,6 +6,8 @@ import VoteButton from "src/Components/VoteButton/VoteButton"
|
||||
import { useVote } from "src/utils/hooks"
|
||||
import { Tag, Vote_Item_Type } from 'src/graphql';
|
||||
import Badge from "src/Components/Badge/Badge"
|
||||
import { toSlug } from "src/utils/helperFunctions"
|
||||
import { createRoute } from "src/utils/routing"
|
||||
|
||||
export type StoryCardType = Pick<Story,
|
||||
| 'id'
|
||||
@@ -36,7 +38,7 @@ export default function StoryCard({ story }: Props) {
|
||||
<img src={story.cover_image} className='h-[200px] w-full object-cover' alt="" />
|
||||
<div className="p-24">
|
||||
<Header author={story.author} date={story.createdAt} />
|
||||
<Link to={`/blog/post/Story/${story.id}`}>
|
||||
<Link to={createRoute({ type: 'story', id: story.id, title: story.title })}>
|
||||
<h2 className="text-h5 font-bolder mt-16">{story.title}</h2>
|
||||
</Link>
|
||||
<p className="text-body4 text-gray-600 mt-8">{story.excerpt}...</p>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Link } from 'react-router-dom'
|
||||
import Avatar from 'src/features/Profiles/Components/Avatar/Avatar'
|
||||
import { useTrendingPostsQuery } from 'src/graphql'
|
||||
import { random } from 'src/utils/helperFunctions'
|
||||
import { createRoute } from 'src/utils/routing'
|
||||
|
||||
export default function TrendingCard() {
|
||||
|
||||
@@ -24,7 +25,7 @@ export default function TrendingCard() {
|
||||
)
|
||||
:
|
||||
trendingPosts.data?.getTrendingPosts.map(post => {
|
||||
return <Link key={post.id} to={`/blog/post/${post.__typename}/${post.id}`} className="border-b py-16 last-of-type:border-b-0">
|
||||
return <Link key={post.id} to={createRoute({ type: 'post', postType: post.__typename!, id: post.id, title: post.title })} className="border-b py-16 last-of-type:border-b-0">
|
||||
<li className="flex items-start gap-8">
|
||||
<Avatar width={24} src={post.author.avatar} />
|
||||
<p className="text-body5 font-medium">{post.title}</p>
|
||||
|
||||
@@ -96,11 +96,24 @@ export default function ContentEditor({ placeholder, initialContent, name }: Pro
|
||||
});
|
||||
|
||||
|
||||
const initContent = `## hello
|
||||
|
||||
|
||||
how are you doing man
|
||||
|
||||
|
||||
what's up with this face of yours
|
||||
`.replace(/\n(?=\n)/g, "\n\n<br/>\n");
|
||||
|
||||
|
||||
console.log(initContent);
|
||||
|
||||
|
||||
return (
|
||||
<div className={`remirror-theme ${styles.wrapper} bg-white`}>
|
||||
<Remirror
|
||||
manager={manager}
|
||||
initialContent={initialContent}
|
||||
initialContent={initContent}
|
||||
>
|
||||
<TextEditorComponents.SaveModule name={name} />
|
||||
<Toolbar />
|
||||
|
||||
@@ -4,6 +4,7 @@ import Button from "src/Components/Button/Button";
|
||||
import { Author } from "src/features/Posts/types";
|
||||
import Avatar from "src/features/Profiles/Components/Avatar/Avatar";
|
||||
import { trimText } from "src/utils/helperFunctions";
|
||||
import { createRoute } from "src/utils/routing";
|
||||
|
||||
interface Props {
|
||||
author: Pick<Author,
|
||||
@@ -17,7 +18,7 @@ export default function AuthorCard({ author }: Props) {
|
||||
return (
|
||||
<div className="bg-white p-16 border-2 border-gray-200 rounded-12">
|
||||
<div className='flex gap-8'>
|
||||
<Link to={`/profile/${author.id}`}>
|
||||
<Link to={createRoute({ type: 'profile', id: author.id, username: author.name })}>
|
||||
<Avatar width={48} src={author.avatar} />
|
||||
</Link>
|
||||
<div className="overflow-hidden">
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useParams } from 'react-router-dom'
|
||||
import LoadingPage from 'src/Components/LoadingPage/LoadingPage'
|
||||
import NotFoundPage from 'src/features/Shared/pages/NotFoundPage/NotFoundPage'
|
||||
import { usePostDetailsQuery } from 'src/graphql'
|
||||
import { capitalize } from 'src/utils/helperFunctions'
|
||||
import { useAppSelector, } from 'src/utils/hooks'
|
||||
import TrendingCard from '../../Components/TrendingCard/TrendingCard'
|
||||
import AuthorCard from './Components/AuthorCard/AuthorCard'
|
||||
@@ -14,7 +15,9 @@ import styles from './styles.module.scss'
|
||||
|
||||
export default function PostDetailsPage() {
|
||||
|
||||
const { type, id } = useParams()
|
||||
const { type: _type, id } = useParams();
|
||||
const type = capitalize(_type);
|
||||
|
||||
const postDetailsQuery = usePostDetailsQuery({
|
||||
variables: {
|
||||
id: Number(id!),
|
||||
|
||||
@@ -92,4 +92,14 @@ export function generateList(component: React.ReactElement, cnt: number) {
|
||||
return Array(cnt).fill(0).map((_, idx) => React.cloneElement(component, { key: idx }))
|
||||
}
|
||||
|
||||
export function toSlug(title: string) {
|
||||
return title.toLowerCase()
|
||||
.replace(/[^\w ]+/g, '')
|
||||
.replace(/ +/g, '-');
|
||||
}
|
||||
|
||||
export function capitalize(s?: string) {
|
||||
return s && s[0].toUpperCase() + s.slice(1);
|
||||
}
|
||||
|
||||
export const withHttp = (url: string) => !/^https?:\/\//i.test(url) ? `http://${url}` : url;
|
||||
1
src/utils/routing/index.ts
Normal file
1
src/utils/routing/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './routes'
|
||||
61
src/utils/routing/routes.ts
Normal file
61
src/utils/routing/routes.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { toSlug } from "../helperFunctions";
|
||||
|
||||
type RouteOptions =
|
||||
| {
|
||||
type: 'post',
|
||||
id: string | number,
|
||||
postType: string,
|
||||
title?: string,
|
||||
username?: string,
|
||||
}
|
||||
| {
|
||||
type: 'story',
|
||||
id: string | number,
|
||||
title?: string,
|
||||
username?: string,
|
||||
}
|
||||
| {
|
||||
type: 'bounty',
|
||||
id: string | number,
|
||||
title?: string,
|
||||
username?: string,
|
||||
}
|
||||
| {
|
||||
type: 'question',
|
||||
id: string | number,
|
||||
title?: string,
|
||||
username?: string,
|
||||
}
|
||||
| {
|
||||
type: 'profile',
|
||||
id: string | number,
|
||||
username?: string,
|
||||
}
|
||||
|
||||
export function createRoute(options: RouteOptions) {
|
||||
|
||||
|
||||
if (options.type === 'post')
|
||||
return `/blog/post/${options.postType}/${options.id}`
|
||||
+ (options.title ? `/${toSlug(options.title)}` : "");
|
||||
|
||||
if (options.type === 'story')
|
||||
return `/blog/post/story/${options.id}`
|
||||
+ (options.title ? `/${toSlug(options.title)}` : "");
|
||||
|
||||
if (options.type === 'bounty')
|
||||
return `/blog/post/bounty/${options.id}`
|
||||
+ (options.title ? `/${toSlug(options.title)}` : "");
|
||||
|
||||
|
||||
if (options.type === 'question')
|
||||
return `/blog/post/question/${options.id}`
|
||||
+ (options.title ? `/${toSlug(options.title)}` : "");
|
||||
|
||||
|
||||
if (options.type === 'profile')
|
||||
return `/profile/${options.id}`
|
||||
+ (options.username ? `/${toSlug(options.username)}` : "");
|
||||
|
||||
return ''
|
||||
}
|
||||
Reference in New Issue
Block a user