...
 
Commits (10)
# build environment
FROM node:12-alpine as build
FROM node:current as build
WORKDIR /app
# RUN npm i -g yarn
COPY package.json ./
COPY yarn.lock ./
......@@ -11,6 +12,7 @@ ARG API_URL
ENV REACT_APP_API_URL=${API_URL}
ARG BUILD_VERSION
ENV REACT_APP_BUILD_VERSION=${BUILD_VERSION}
ENV GENERATE_SOURCEMAP=false
RUN npm run build
# production environment
......
......@@ -4,6 +4,7 @@
"private": true,
"dependencies": {
"@apollo/react-hooks": "^3.1.0",
"@ckeditor/ckeditor5-react": "^2.1.0",
"@types/google-libphonenumber": "^7.4.17",
"@types/jest": "24.0.18",
"@types/node": "12.7.5",
......@@ -19,18 +20,20 @@
"react": "^16.9.0",
"react-big-calendar": "^0.22.1",
"react-dom": "^16.9.0",
"react-emoji-render": "^1.2.1",
"react-markdown": "^4.2.2",
"react-router": "^5.1.2",
"react-router-dom": "^5.1.2",
"react-scripts": "^3.2.0",
"react-textarea-autosize": "^7.1.2",
"semantic-ui-react": "^0.88.1",
"sigma-ckeditor5-build": "https://gitlab.binets.fr/br/sigma-ckeditor5-build.git",
"typescript": "^3.6.3",
"use-debounce": "^3.3.0"
},
"scripts": {
"start": "craco start",
"build": "craco build",
"build": "GENERATE_SOURCEMAP=false craco build",
"test": "craco test",
"eject": "craco eject"
},
......
......@@ -274,6 +274,7 @@ type Mutation {
participationPermission: ConnectionStatus
): User
editPassword(newPassword: String!): Boolean
generateICalID(type: CalendarType!): String
editMessage(messageToEdit: ID!, title: String, content: String): Message
removeMessage(messageToRemove: ID!): Boolean
likeGroup(gid: ID!): Boolean
......
......@@ -25,6 +25,8 @@ import ForgottenPassword from "./pages/forgottenPassword/ForgottenPassword";
import ResetPassword from "./pages/ResetPassword/ResetPassword";
import VerifyEmail from "./pages/VerifyEmail/VerifyEmail";
import CreateGroup from "./pages/createGroup/CreateGroup";
import Page404 from "./pages/404";
import ICal from "./pages/ical/ICal";
const NotLoggedInRoutes = () => (
<Switch>
......@@ -49,6 +51,7 @@ const LoggedInRoutes = () => (
<Route exact path={ROUTES.EVENT} component={EventPage}/>
<Route exact path={ROUTES.EVENTS} component={EventsPage}/>
<Route path={ROUTES.GROUP} component={GroupPage}/> {/* Group need not to have exact */}
<Route exact path={ROUTES.ICAL} component={ICal}/>
<Route exact path={ROUTES.LOGIN} render={() => <Redirect to={consumeAfterLogin() || ROUTES.ME}/>}/>
<Route exact path={ROUTES.ME} component={UserPage}/>
<Route exact path={ROUTES.MESSAGE} component={MessagePage}/>
......@@ -57,6 +60,7 @@ const LoggedInRoutes = () => (
<Route exact path={ROUTES.TOL} component={TOLPage}/>
<Route exact path={ROUTES.USER} component={UserPage}/>
<Route exact path={ROUTES.VERIFY_EMAIL} component={VerifyEmail}/>
<Route component={Page404}/>
</Switch>
);
......
import React, {ChangeEvent, useState} from "react";
import React, {useState} from "react";
import {Form} from "semantic-ui-react";
import {useMutation} from "@apollo/react-hooks";
import {gql} from "apollo-boost";
import {MessageBase, messageBase} from "../../services/apollo/fragments/message";
import GraphQLError from "../Messages/Errors";
import TextareaAutosize from "react-textarea-autosize";
import SendAsDropdown from "./SendAsDropdown";
import TextEditor from "../TextEditor";
export interface CommentFormProps {
parent: string
......@@ -71,12 +71,10 @@ function CommentForm(props: CommentFormProps) {
value={title}
onChange={(_, {value}) => setTitle(value)}
/>
<Form.Field
label='Comment'
placeholder='You can use Markdown !'
value={content}
control={TextareaAutosize}
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => setContent(e.target.value)}/>
<Form.Field>
<label>Comment</label>
<TextEditor text={content} setText={setContent} placeholder="Leave a comment!"/>
</Form.Field>
<SendAsDropdown asGroup={asGroup} setAsGroup={setAsGroup}/>
<Form.Button
type="submit"
......
import React from "react";
import ReactMarkdown from "react-markdown";
import {parseEmojis} from "../services/emojis";
export interface MarkdownDisplayerProps {
text: string
}
function MarkdownDisplayer(props: MarkdownDisplayerProps) {
const {text} = props;
return <ReactMarkdown source={parseEmojis(text)}/>;
}
export default MarkdownDisplayer;
......@@ -7,7 +7,7 @@ import {RoutesBuilders} from "../../constants/routes";
import MessageAuthors from "./MessageAuthors";
import MessageRecipients from "./MessageRecipients";
import {MessageBase} from "../../services/apollo/fragments/message";
import ReactMarkdown from "react-markdown";
import MarkdownDisplayer from "../MarkdownDisplayer";
export function MessageFeedItem(props: { m: MessageExtended | MessageBase }) {
const {m} = props;
......@@ -32,7 +32,7 @@ export function MessageFeedItem(props: { m: MessageExtended | MessageBase }) {
{m.title}
</Feed.Summary>
<Feed.Extra text>
<ReactMarkdown source={m.content}/>
<MarkdownDisplayer text={m.content}/>
</Feed.Extra>
<Feed.Meta>
{m.children && m.children.length > 0 && <>{m.children.length} answers</>}
......
import React, {ChangeEvent} from "react";
// @ts-ignore
import CKEditor from '@ckeditor/ckeditor5-react';
// @ts-ignore
import ClassicEditor from 'sigma-ckeditor5-build';
export interface TextEditorProps {
text: string
setText: (t: string) => void
placeholder?: string
}
function TextEditor(props: TextEditorProps) {
const {text, setText, placeholder} = props;
return (
<CKEditor
editor={ClassicEditor}
data={text}
onChange={(event: ChangeEvent, editor: any) => setText(editor.getData())}
placeholder={placeholder}
/>
);
}
export default TextEditor;
export const API_URL = process.env.REACT_APP_API_URL || "http://localhost:3000";
export const GRAPHQL_URL = API_URL + "/graphql";
export const BUILD_VERSION = process.env.BUILD_VERSION || 'dev';
export const BUILD_VERSION = process.env.REACT_APP_BUILD_VERSION || 'dev';
export const PUBLIC_URL = process.env.REACT_APP_BASENAME;
......@@ -8,6 +8,7 @@ export const ROUTES = {
FORGOTTEN_PASSWORD: '/forgotten-password',
GROUP: '/group/:gid',
GROUPS: '/groups',
ICAL: '/ical',
LOGIN: '/login',
ME: '/me',
MESSAGE: '/message/:mid',
......
......@@ -16,5 +16,12 @@ export interface Event extends EventExtended {
export enum PERMISSION_STATUS {
ALL = 'all',
PARTICIPANT = 'participant',
ORGANIZER = 'organizer',
ORGANIZER = 'organizer',
}
export enum CALENDAR_TYPE {
ALL = 'ALL',
FOLLOWER = 'FOLLOWER',
MEMBER = 'MEMBER',
PARTICIPATING = 'PARTICIPATING',
}
import React from "react";
import {Container, Message} from "semantic-ui-react";
import {ROUTES} from "../constants/routes";
import {Link, useLocation} from "react-router-dom";
function Page404() {
const location = useLocation();
return <Container>
<Message
error
header="404 not found"
content={<>
<p>
We couldn't find the page you are looking for : {location.pathname}
</p>
<p>
Get back <Link to={ROUTES.MESSAGES}>home</Link>
</p>
</>}
/>
</Container>;
}
export default Page404;
......@@ -7,8 +7,8 @@ import {Group} from "../../constants/types";
import {LoadingMessage} from "../../components/Messages/LoadingMessage";
import {RoutesBuilders} from "../../constants/routes";
import {useHistory} from "react-router-dom";
import ReactMarkdown from "react-markdown";
import GraphQLError from "../../components/Messages/Errors";
import MarkdownDisplayer from "../../components/MarkdownDisplayer";
const SEARCH_GROUP = gql`
query SearchGroup($search: String) {
......@@ -65,7 +65,7 @@ function GroupCard(props: GroupCardProps) {
meta={"@" + g.gid}
description={
<div style={{maxHeight: '20em', overflow: 'hidden'}}>
<ReactMarkdown source={g.description || ""}/>
<MarkdownDisplayer text={g.description || ""}/>
</div>
}
/>
......
import React, {ChangeEvent, useContext, useState} from "react";
import React, {useContext, useState} from "react";
import {
Container,
Divider,
......@@ -21,7 +21,7 @@ import {EventBase, eventBase} from "../../services/apollo/fragments/event";
import UserContext from "../../components/UserContext/context";
import {makeMomentFromDateAndTime} from "../../services/date";
import GraphQLError from "../../components/Messages/Errors";
import TextareaAutosize from "react-textarea-autosize";
import TextEditor from "../../components/TextEditor";
const CREATE_EVENT = gql`
mutation createEvent(
......@@ -134,18 +134,15 @@ function CreateEventPage() {
</Segment>
<Segment>
<Form.Input
label="title"
label="Title"
placeholder="title"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<Form.Field
control={TextareaAutosize}
label="content"
placeholder="content"
value={content}
onChange={(e: ChangeEvent<HTMLInputElement>) => setContent(e.target.value)}
/>
<Form.Field>
<label>Content</label>
<TextEditor text={content} setText={setContent} placeholder="Describe here your event!"/>
</Form.Field>
<Form.Input
label="location"
placeholder="location"
......
import React, {ChangeEvent, useContext, useEffect, useState} from "react";
import React, {useContext, useEffect, useState} from "react";
import {Button, Container, Dropdown, Form, Header, Message} from "semantic-ui-react";
import {gql} from "apollo-boost";
import {useMutation} from "@apollo/react-hooks";
......@@ -7,7 +7,7 @@ import {useHistory} from "react-router-dom";
import {RoutesBuilders} from "../../constants/routes";
import UserContext from "../../components/UserContext/context";
import GraphQLError from "../../components/Messages/Errors";
import TextareaAutosize from "react-textarea-autosize";
import TextEditor from "../../components/TextEditor";
const CREATE_GROUP = gql`
mutation createGroup($fromGroup: ID!, $name: String!, $description: String, $mail: String, $school: String, $website: String) {
......@@ -69,13 +69,14 @@ function CreateGroup() {
value={name}
onChange={(_, {value}) => setName(value)}
/>
<Form.Field
control={TextareaAutosize}
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => setDescription(e.target.value)}
label="Description"
placeholder="Le Binet Tchoupix permet à ses adérents d'accéder à une collection impressionante d'albums, et à des conférences sur le sujet"
value={description}
/>
<Form.Field>
<label>Description</label>
<TextEditor
text={description}
setText={setDescription}
placeholder="Le Binet Tchoupix permet à ses adérents d'accéder à une collection impressionante d'albums, et à des conférences sur le sujet"
/>
</Form.Field>
<Form.Input
label="Mail"
placeholder="tchoupix@eleves.polytechnique.fr"
......
import React, {ChangeEvent, useState} from "react";
import React, {useState} from "react";
import {Container, Form, Header, Label} from "semantic-ui-react";
import {gql} from "apollo-boost";
import {useMutation} from "@apollo/react-hooks";
......@@ -8,8 +8,8 @@ import {RoutesBuilders} from "../../constants/routes";
import GroupSearch from "../../components/GroupSearch/GroupSearch";
import {GroupBase} from "../../services/apollo/fragments/groupBase";
import GraphQLError from "../../components/Messages/Errors";
import TextareaAutosize from "react-textarea-autosize";
import SendAsDropdown from "../../components/CommentForm/SendAsDropdown";
import TextEditor from "../../components/TextEditor";
const CREATE = gql`
mutation createMessage ($title: String!, $content: String!, $toGroups: [ID!]) {
......@@ -75,13 +75,10 @@ function CreateMessagePage() {
value={title}
onChange={(_, {value}) => setTitle(value)}
/>
<Form.Field
label='Comment'
placeholder='You can use Markdown !'
value={content}
control={TextareaAutosize}
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => setContent(e.target.value)}
/>
<Form.Field>
<label>Comment</label>
<TextEditor text={content} setText={setContent} placeholder='You can use Markdown !'/>
</Form.Field>
<Form.Group inline>
<SendAsDropdown setAsGroup={setAsGroup} asGroup={asGroup}/>
<span style={{minWidth: 10}}/>
......
......@@ -7,7 +7,6 @@ import GraphQLError from "../../components/Messages/Errors";
import {Button, Container, Feed, Header, List, Segment} from "semantic-ui-react";
import {Link, useParams} from "react-router-dom";
import {RoutesBuilders} from "../../constants/routes";
import ReactMarkdown from "react-markdown";
import ParticipateButton from "./components/ParticipateButton";
import UserContext from "../../components/UserContext/context";
import EditEvent from "./components/EditEvent";
......@@ -16,6 +15,7 @@ import {ParticipatingGroups, ParticipatingUsers} from "../events/components/Part
import MessageFeedItem from "../../components/Message/MessageFeedItem";
import CreatePost from "./components/CreatePost";
import {PERMISSION_STATUS} from "../../constants/types";
import MarkdownDisplayer from "../../components/MarkdownDisplayer";
const GET_EVENT = gql`
query event($eid: ID!) {
......@@ -34,10 +34,10 @@ function EventPage() {
const [edit, setEdit] = useState<boolean>(false);
if (loading)
return <LoadingMessage/>;
return <Container><LoadingMessage/></Container>;
if (!data || !data.event || error)
return <GraphQLError error={error}/>;
return <Container><GraphQLError error={error}/></Container>;
const e: EventWithParticipation = data.event;
const isAdmin = user && e.authors && user.speakerOf.map(g => g.gid).indexOf(e.authors[0].gid) !== -1;
......@@ -75,7 +75,7 @@ function EventPage() {
</Segment>
: <>
<Segment vertical>
<ReactMarkdown source={e.content}/>
<MarkdownDisplayer text={e.content}/>
</Segment>
<Segment vertical>
<List relaxed>
......
import React, {ChangeEvent, useState} from "react";
import React, {useState} from "react";
import {Form} from "semantic-ui-react";
import {useMutation} from "@apollo/react-hooks";
import {gql} from "apollo-boost";
import {MessageBase, messageBase} from "../../../services/apollo/fragments/message";
import GraphQLError from "../../../components/Messages/Errors";
import TextareaAutosize from "react-textarea-autosize";
import TextEditor from "../../../components/TextEditor";
const COMMENT = gql`
mutation createUserPrivateEventPost($forEvent: ID!, $content: String!, $title: String!) {
......@@ -46,13 +46,10 @@ function CreatePost (props: CreatePostProps) {
value={title}
onChange={(_, {value}) => setTitle(value)}
/>
<Form.Field
control={TextareaAutosize}
label='Comment'
placeholder='You can use Markdown !'
value={content}
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => setContent(e.target.value)}
/>
<Form.Field>
<label>Comment</label>
<TextEditor text={content} setText={setContent} placeholder="The content of your message..."/>
</Form.Field>
<Form.Button type="submit" loading={loading}>Submit</Form.Button>
{error && <GraphQLError error={error}/>}
</Form>
......
import React, {ChangeEvent, useState} from "react";
import React, {useState} from "react";
import {eventExtended, EventExtended} from "../../../services/apollo/fragments/event";
import {Form, Message} from "semantic-ui-react";
import {gql} from "apollo-boost";
......@@ -8,7 +8,7 @@ import {makeMomentFromDateAndTime} from "../../../services/date";
import moment from "moment";
import {Date} from "../../../components/date";
import GraphQLError from "../../../components/Messages/Errors";
import TextareaAutosize from "react-textarea-autosize";
import TextEditor from "../../../components/TextEditor";
const EDIT_EVENT = gql`
mutation editEvent (
......@@ -80,14 +80,10 @@ function EditEvent(props: EditEventProps) {
/>
<Form.Field>
<label>Content</label>
<Form.Field
label='Comment'
placeholder='You can use Markdown !'
value={content}
control={TextareaAutosize}
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => setContent(e.target.value)}
/>
<Form.Field>
<label>Comment</label>
<TextEditor text={content} setText={setContent} placeholder='You can use Markdown !'/>
</Form.Field>
</Form.Field>
<Form.Input
label="Location"
......@@ -108,7 +104,7 @@ function EditEvent(props: EditEventProps) {
/>
soit <Date m={makeMomentFromDateAndTime(startDate, startTime)}/>
</Form.Group>
<Form.Group inline error={error}>
<Form.Group inline error={error || undefined}>
<label>Fin : </label>
<Form.Input
type="date"
......
import React, {useState} from "react";
import {Button, Container, Feed, Header, Message, Segment} from "semantic-ui-react";
import {Button, ButtonGroup, Container, Feed, Header, Message, Segment} from "semantic-ui-react";
import {gql} from "apollo-boost";
import {EventBase, EventExtended, eventExtended} from "../../services/apollo/fragments/event";
import {useQuery} from "@apollo/react-hooks";
......@@ -7,6 +7,8 @@ import {LoadingMessage} from "../../components/Messages/LoadingMessage";
import EventFeedItem from "../../components/Events/EventFeedItem";
import Calendar from "./components/Calendar";
import GraphQLError from "../../components/Messages/Errors";
import {Link} from "react-router-dom";
import {ROUTES} from "../../constants/routes";
const GET_EVENTS = gql`
query getEvents {
......@@ -48,13 +50,15 @@ function EventsPage() {
return (
<Container>
<Segment basic>
<Button
basic
content={calendar ? "View list" : "View calendar"}
floated="right"
icon={calendar ? "list" : "calendar"}
onClick={() => setCalendar(!calendar)}
/>
<ButtonGroup floated="right">
<Button
basic
content={calendar ? "View list" : "View calendar"}
icon={calendar ? "list" : "calendar"}
onClick={() => setCalendar(!calendar)}
/>
<Button basic icon="download" content="Download" as={Link} to={ROUTES.ICAL}/>
</ButtonGroup>
<Header as="h1" content="Nexts events"/>
</Segment>
<Segment basic>
......
import React, {useState} from 'react';
import {Button, Form, Header, Segment} from 'semantic-ui-react';
import ReactMarkdown from "react-markdown";
import gql from "graphql-tag";
import {groupBase} from "../../../services/apollo/fragments/groupBase";
import {useMutation} from "@apollo/react-hooks";
......@@ -10,6 +9,7 @@ import MemberRequests from "./MemberRequests";
import GraphQLError from "../../../components/Messages/Errors";
import TextareaAutosize from "react-textarea-autosize";
import {ChangeLogo} from "../../../components/ChangeImage/ChangeImage";
import MarkdownDisplayer from "../../../components/MarkdownDisplayer";
const EDIT_GROUP_DESCRIPTION = gql`
mutation editGroupDescription($gid: ID!, $description: String!) {
......@@ -147,7 +147,7 @@ export function GroupAdministrer({group, refetch}: GroupAdministrerProps) {
name="Description"
placeholder="La description du groupe. Tu peux utiliser du Markdown !"
inputComponent={TextareaAutosize}
displayComponent={ReactMarkdown}
displayComponent={MarkdownDisplayer}
/>
<EditVariable
......
import React, {ChangeEvent, useState} from "react";
import ReactMarkdown from "react-markdown";
import React, {useState} from "react";
import {Button, Divider, Form, Header, Label, Message, Segment} from "semantic-ui-react";
import {gql} from "apollo-boost";
import {groupBase} from "../../../services/apollo/fragments/groupBase";
import {useMutation} from "@apollo/react-hooks";
import GraphQLError from "../../../components/Messages/Errors";
import {useParams} from "react-router-dom";
import TextareaAutosize from "react-textarea-autosize";
import MarkdownDisplayer from "../../../components/MarkdownDisplayer";
import TextEditor from "../../../components/TextEditor";
export interface FrontPageProps {
frontPage: string
......@@ -53,12 +53,11 @@ function FrontPage(props: FrontPageProps) {
{error && <GraphQLError error={error}/>}
{edition && <>
<Form>
<Form.Field
placeholder='Write here what the user will see on your frontPage. You can use Markdown !'
value={value}
control={TextareaAutosize}
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => setValue(e.target.value)}
/>
<Form.Field>
<label>Frontpage text</label>
<TextEditor text={value} setText={setValue}
placeholder='Write here what the user will see on your frontPage'/>
</Form.Field>
<Form.Group>
<Form.Button
color="green"
......@@ -84,7 +83,7 @@ function FrontPage(props: FrontPageProps) {
</Header>
<Divider hidden/>
</>}
<ReactMarkdown source={value}/>
<MarkdownDisplayer text={value}/>
</Segment>
</>
);
......
......@@ -6,7 +6,7 @@ import {requestBase} from "../../../services/apollo/fragments/request";
import {useMutation} from "@apollo/react-hooks";
import {GroupBase} from "../../../services/apollo/fragments/groupBase";
import GraphQLError from "../../../components/Messages/Errors";
import TextareaAutosize from "react-textarea-autosize";
import TextEditor from "../../../components/TextEditor";
const MAKE_REQUEST = gql`
mutation makeRequest($gid: ID!, $comment: String) {
......@@ -82,11 +82,8 @@ function MakeRequestButton(props: RequestButtonProps) {
<Form onSubmit={() => makeRequest({variables: {gid: g.gid, comment}})}>
<Form.Field>
<label>Commentaire</label>
<TextareaAutosize
placeholder="Leave a comment to the group admin !"
value={comment}
onChange={(e) => 'value' in e.target && setComment(e.target.value)}
/>
<TextEditor text={comment} setText={setComment}
placeholder="Leave a comment to the group admin !"/>
</Form.Field>
<Button.Group fluid>
<Button
......
......@@ -8,7 +8,7 @@ import {GroupBase, groupBase} from "../../../services/apollo/fragments/groupBase
import {LoadingMessage} from "../../../components/Messages/LoadingMessage";
import GraphQLError from "../../../components/Messages/Errors";
import {useParams} from "react-router-dom";
import TextareaAutosize from "react-textarea-autosize";
import TextEditor from "../../../components/TextEditor";
export interface MessagesPageProps {
privateMessages?: boolean
......@@ -90,11 +90,7 @@ function PostForm(props: { gid: string, refetch: () => void, publicMode: boolean
/>
<Form.Field>
<label>Comment</label>
<TextareaAutosize
placeholder='You can use Markdown !'
value={content}
onChange={(e) => setContent(e.target.value)}
/>
<TextEditor text={content} setText={setContent} placeholder='You can use Markdown !'/>
</Form.Field>
<Form.Button type="submit" loading={loading}>Submit</Form.Button>
{error && <GraphQLError error={error}/>}
......
import React, {useEffect, useState} from "react";
import {Button, Container, Header} from "semantic-ui-react";
import {gql} from "apollo-boost";
import {CALENDAR_TYPE} from "../../constants/types";
import {useMutation, useQuery} from "@apollo/react-hooks";
import {LoadingMessage} from "../../components/Messages/LoadingMessage";
import GraphQLError from "../../components/Messages/Errors";
import {API_URL} from "../../constants/config";
const ICAL = gql`
query ical($type: CalendarType!) {
iCalID(type: $type)
}
`;
const GENERATE_ICAL = gql`
mutation generateICal($type: CalendarType!) {
generateICalID(type: $type)
}
`;
interface QueryParams {
type: string
}
interface QueryResults {
iCalID?: string
}
interface MutationResults {
generateICalID: string
}
function computeICalLink(token: string | undefined) {
return token ? API_URL + '/calendar/ical/' + token : undefined;
}
function DisplayICalLink(props: { type: CALENDAR_TYPE }) {
const {type} = props;
const [link, setLink] = useState<string | undefined>(undefined);
const [generate, mutation] = useMutation<MutationResults, QueryParams>(GENERATE_ICAL, {
variables: {type},
});
const query = useQuery<QueryResults, QueryParams>(ICAL, {
variables: {type},
fetchPolicy: "no-cache",
});
useEffect(() => {
setLink(computeICalLink(mutation.data && mutation.data.generateICalID)
|| computeICalLink(query.data && query.data.iCalID)
|| undefined)
}, [query.data, mutation.data]);
const error = query.error || mutation.error;
return <>
{query.loading && <LoadingMessage/>}
{error && <GraphQLError/>}
<Button.Group>
<Button
fluid
icon="download"
href={link}
content="Download"
disabled={!link}
/>}
<Button
onClick={() => generate()}
icon={mutation.data ? 'check' : 'sync'}
content="Générer"
disabled={!!mutation.data}
/>
</Button.Group>
</>
}
interface CategoryType {
type: CALENDAR_TYPE,
title: string
}
const categories: CategoryType[] = [
{type: CALENDAR_TYPE.ALL, title: "All events"},
{type: CALENDAR_TYPE.FOLLOWER, title: "From group I like"},
{type: CALENDAR_TYPE.MEMBER, title: "From groups I am member of"},
{type: CALENDAR_TYPE.PARTICIPATING, title: "Only the events I participate"},
];
function ICal() {
return (
<Container>
<Header as="h1">
Récupérer mes liens iCal
</Header>
{categories.map(({type, title}) => <>
<Header as="h4" content={title}/>
<DisplayICalLink type={type}/>
</>)}
</Container>
);
}
export default ICal;
import {MessageExtended} from "../../../services/apollo/fragments/messageExtended";
import {Divider, Feed, Header, List, Segment} from "semantic-ui-react";
import ReactMarkdown from "react-markdown";
import React from "react";
import {Link} from "react-router-dom";
import {RoutesBuilders} from "../../../constants/routes";
......@@ -9,6 +8,7 @@ import MessageRecipients from "../../../components/Message/MessageRecipients";
import {Date} from "../../../components/date";
import MessageFeedItem from "../../../components/Message/MessageFeedItem";
import CommentForm from "../../../components/CommentForm/CommentForm";
import MarkdownDisplayer from "../../../components/MarkdownDisplayer";
export interface DisplayMessageProps {
m: MessageExtended,
......@@ -38,14 +38,14 @@ export function DisplayMessage(props: DisplayMessageProps) {
</List.Item>
</List>
<Divider/>
<ReactMarkdown source={m.content}/>
<MarkdownDisplayer text={m.content}/>
<Divider hidden/>
{m.forEvent && <>
<Segment>
<Header icon="calendar" content="Related event" as="h3"/>
<Header size="small" as={Link} to={RoutesBuilders.Event(m.forEvent.eid)}
content={m.forEvent.title}/>
<ReactMarkdown source={m.forEvent.content}/>
<MarkdownDisplayer text={m.forEvent.content}/>
</Segment>
<Divider hidden/>
</>}
......@@ -53,7 +53,7 @@ export function DisplayMessage(props: DisplayMessageProps) {
<Segment>
<Header icon="mail" content="Parent " as="h3"/>
<Header size="small" as={Link} to={RoutesBuilders.Message(m.parent.mid)} content={m.parent.title}/>
<ReactMarkdown source={m.parent.content}/>
<MarkdownDisplayer text={m.parent.content}/>
</Segment>
<Divider hidden/>
</>}
......
import React, {ChangeEvent, useState} from "react";
import React, {useState} from "react";
import {MessageBase, messageBase} from "../../../services/apollo/fragments/message";
import {gql} from "apollo-boost";
import {Button, Form, Header, Segment} from "semantic-ui-react";
import {useMutation} from "@apollo/react-hooks";
import GraphQLError from "../../../components/Messages/Errors";
import {MessageExtended} from "../../../services/apollo/fragments/messageExtended";
import TextareaAutosize from "react-textarea-autosize";
import TextEditor from "../../../components/TextEditor";
const EDIT_MESSAGE = gql`
mutation editMessage($title: String, $mid: ID!, $content: String) {
......@@ -62,13 +62,7 @@ function EditMessage(props: EditMessageProps) {
/>
<Form.Field>
<label>Content</label>
<Form.Field
placeholder='Write here what the user will see on your frontPage. You can use Markdown !'
value={content}
control={TextareaAutosize}
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => setContent(e.target.value)}
/>
<TextEditor text={content} setText={setContent} placeholder='You can use Markdown !'/>
</Form.Field>
<Button
color="yellow"
......
......@@ -36,7 +36,7 @@ function minID(messages: MessageExtended[]) {
function MessagesPage() {
const {data, loading, error, fetchMore} = useQuery<{ allPrivatePosts: MessageExtended[] }, QueryParams>(PRIVATE_POSTS, {
errorPolicy: "ignore",
fetchPolicy: 'cache-and-network',
fetchPolicy: 'cache-first',
notifyOnNetworkStatusChange: true,
variables: {limit: NB_MESSAGES_IN_A_QUERY},
});
......
......@@ -3,7 +3,7 @@ import {Accordion, Icon, Image, List} from "semantic-ui-react";
import {Link} from "react-router-dom";
import {Group} from "../../../constants/types";
import {RoutesBuilders} from "../../../constants/routes";
import ReactMarkdown from "react-markdown";
import MarkdownDisplayer from "../../../components/MarkdownDisplayer";
interface GroupItemProps {
group: Group,
......@@ -72,7 +72,7 @@ export function UserMemberships(props: UserMembershipsProps) {
{gr.name}
</List.Header>
<List.Description>
<ReactMarkdown source={gr.description}/>
<MarkdownDisplayer text={gr.description || ""}/>
</List.Description>
{expanded && gr.parents && (
<GroupItemAccordion group={gr}/>
......
// @ts-ignore
import {toArray} from "react-emoji-render";
export const parseEmojis = (value: string) => {
const emojisArray = toArray(value);
// toArray outputs React elements for emojis and strings for other
return emojisArray.reduce((previous: any, current: any) => {
if (typeof current === "string") {
return previous + current;
}
return previous + current.props.children;
}, "");
};
This diff is collapsed.