Skip to content
Snippets Groups Projects
Commit 454ce8a2 authored by xaralis's avatar xaralis
Browse files

feat: edit post content using markdown

parent 031a68d0
Branches
No related tags found
No related merge requests found
......@@ -8,7 +8,8 @@ import Footer from "components/Footer";
import Navbar from "components/Navbar";
import Home from "pages/Home";
import Program from "pages/Program";
import { AuthStore } from "stores";
import { AuthStore, PostStore } from "stores";
import { updateWindowPosts } from "utils";
import keycloak from "./keycloak";
......@@ -26,7 +27,7 @@ if (process.env.REACT_APP_SENTRY_DSN) {
const onKeycloakEvent = (event) => {
if (["onAuthRefreshSuccess", "onAuthSuccess"].includes(event)) {
Sentry.setUser(keycloak.tokenParsed);
AuthStore.update((state) => {
const kcRoles = keycloak.tokenParsed.roles;
let role = null;
......@@ -38,6 +39,7 @@ const onKeycloakEvent = (event) => {
role = "regp";
}
AuthStore.update((state) => {
state.isAuthenticated = true;
state.user = {
name: keycloak.tokenParsed.name,
......@@ -46,6 +48,12 @@ const onKeycloakEvent = (event) => {
accessToken: keycloak.token,
};
});
PostStore.update((state) => {
// Only display proposals verified by chairman to other users.
state.filters.showPendingProposals = role === "chairman";
updateWindowPosts(state);
});
}
};
......
import React from "react";
import ReactMde from "react-mde";
import Showdown from "showdown";
const converter = new Showdown.Converter({
tables: true,
simplifiedAutoLink: true,
strikethrough: true,
tasklists: true,
});
const MarkdownEditor = ({ value, onChange }) => {
return (
<ReactMde
value={value}
onChange={onChange}
// selectedTab={selectedTab}
// onTabChange={setSelectedTab}
generateMarkdownPreview={(markdown) =>
Promise.resolve(converter.makeHtml(markdown))
}
/>
);
};
export default MarkdownEditor;
......@@ -3,6 +3,7 @@ import React, { useState } from "react";
import Button from "components/Button";
import { Card, CardActions, CardBody, CardHeadline } from "components/cards";
import ErrorMessage from "components/ErrorMessage";
import MarkdownEditor from "components/mde/MarkdownEditor";
import Modal from "components/modals/Modal";
const AnnouncementEditModal = ({
......@@ -14,19 +15,26 @@ const AnnouncementEditModal = ({
...props
}) => {
const [text, setText] = useState(announcement.content);
const [noTextError, setNoTextError] = useState(false);
const onTextInput = (evt) => {
setText(evt.target.value);
const onTextInput = (newText) => {
setText(newText);
if (newText !== "") {
setNoTextError(false);
}
};
const confirm = (evt) => {
if (!!text) {
onConfirm(text);
} else {
setNoTextError(true);
}
};
return (
<Modal containerClassName="max-w-md" onRequestClose={onCancel} {...props}>
<Modal containerClassName="max-w-lg" onRequestClose={onCancel} {...props}>
<Card>
<CardBody>
<div className="flex items-center justify-between mb-4">
......@@ -35,20 +43,20 @@ const AnnouncementEditModal = ({
<i className="ico--close"></i>
</button>
</div>
<div className="form-field">
<label className="form-field__label" htmlFor="field">
Nový text oznámení
</label>
<div className="form-field__wrapper form-field__wrapper--shadowed">
<textarea
className="text-input form-field__control text-base"
<MarkdownEditor
value={text}
rows="8"
placeholder="Vyplňte text oznámení"
onChange={onTextInput}
></textarea>
</div>
</div>
error={
noTextError
? "Před úpravou oznámení nezapomeňte vyplnit jeho obsah."
: null
}
placeholder="Vyplňte text oznámení"
toolbarCommands={[
["bold", "italic", "strikethrough"],
["link", "unordered-list", "ordered-list"],
]}
/>
{error && (
<ErrorMessage className="mt-2">
Při editaci došlo k problému: {error}
......
.mde-header {
background: transparent;
}
.react-mde .invisible {
display: none;
}
.mde-header {
border: 0;
align-items: center;
}
.mde-header .mde-tabs {
display: inline-flex;
background-color: #000;
background-color: rgba(0,0,0,1);
padding: .25rem;
}
.mde-header .mde-tabs button {
padding: .5rem 1rem;
font-family: Bebas Neue,Helvetica,Arial,sans-serif;
font-weight: 400;
font-size: 1.1rem;
--text-opacity: 1;
color: #fff;
color: rgba(255,255,255,var(--text-opacity));
text-align: center;
cursor: pointer;
border: 0;
border-radius: 0;
margin: 0 !important;
outline: 0;
}
.mde-header .mde-tabs button.selected {
/* blue-300 */
background: #027da8;
color: #fff;
border: 0;
border-radius: 0;
margin: 0;
outline: 0;
}
.mde-header .mde-tabs button:not(.selected):hover {
/* grey-500 */
background: #303132;
}
.mde-header .mde-header-item {
border: 1px transparent solid;
transition: border-color 100ms ease-in-out;
}
.mde-header ul.mde-header-group li.mde-header-item {
margin: 0;
}
.mde-header .mde-header-item:hover {
/* grey-200 */
border: 1px #adadad solid;
}
.mde-text {
font-family: monospace;
}
.mde-header ul.mde-header-group {
padding: 0 0.5rem;
}
.mde-header ul.mde-header-group + .mde-header-group {
margin-left: .5rem;
}
import React, { useState } from "react";
import ReactMde from "react-mde";
import classNames from "classnames";
import Showdown from "showdown";
import "react-mde/lib/styles/css/react-mde-toolbar.css";
import "./MarkdownEditor.css";
const converter = new Showdown.Converter({
tables: true,
simplifiedAutoLink: true,
strikethrough: true,
tasklists: true,
});
const MarkdownEditor = ({
value,
onChange,
error,
placeholder = "",
...props
}) => {
const [selectedTab, setSelectedTab] = useState("write");
const classes = {
preview: "content-block p-2 border border-grey-200",
textArea: "p-2 text-input text-base",
};
const l18n = {
write: "Psaní",
preview: "Náhled",
uploadingImage: "Nahrávám obrázek",
};
const childProps = {
textArea: {
placeholder,
},
};
return (
<div className={classNames("form-field", { "form-field--error": !!error })}>
<ReactMde
value={value}
onChange={onChange}
selectedTab={selectedTab}
onTabChange={setSelectedTab}
generateMarkdownPreview={(markdown) =>
Promise.resolve(converter.makeHtml(markdown))
}
classes={classes}
l18n={l18n}
childProps={childProps}
{...props}
/>
{error && <div className="form-field__error">{error}</div>}
</div>
);
};
export default MarkdownEditor;
......@@ -3,6 +3,7 @@ import React, { useState } from "react";
import Button from "components/Button";
import { Card, CardActions, CardBody, CardHeadline } from "components/cards";
import ErrorMessage from "components/ErrorMessage";
import MarkdownEditor from "components/mde/MarkdownEditor";
import Modal from "components/modals/Modal";
const PostEditModal = ({
......@@ -14,19 +15,26 @@ const PostEditModal = ({
...props
}) => {
const [text, setText] = useState(post.content);
const [noTextError, setNoTextError] = useState(false);
const onTextInput = (evt) => {
setText(evt.target.value);
const onTextInput = (newText) => {
setText(newText);
if (newText !== "") {
setNoTextError(false);
}
};
const confirm = (evt) => {
if (!!text) {
onConfirm(text);
} else {
setNoTextError(true);
}
};
return (
<Modal containerClassName="max-w-md" onRequestClose={onCancel} {...props}>
<Modal containerClassName="max-w-xl" onRequestClose={onCancel} {...props}>
<Card>
<CardBody>
<div className="flex items-center justify-between mb-4">
......@@ -35,20 +43,16 @@ const PostEditModal = ({
<i className="ico--close"></i>
</button>
</div>
<div className="form-field">
<label className="form-field__label" htmlFor="field">
Nový text příspěvku
</label>
<div className="form-field__wrapper form-field__wrapper--shadowed">
<textarea
className="text-input form-field__control text-base"
<MarkdownEditor
value={text}
rows="8"
placeholder="Vyplňte text příspěvku"
onChange={onTextInput}
></textarea>
</div>
</div>
error={
noTextError
? "Před upravením příspěvku nezapomeňte vyplnit jeho obsah."
: null
}
placeholder="Vyplňte text příspěvku"
/>
{error && (
<ErrorMessage className="mt-2">
Při editaci došlo k problému: {error}
......
......@@ -4,6 +4,7 @@ import classNames from "classnames";
import { addAnnouncement } from "actions/announcements";
import Button from "components/Button";
import ErrorMessage from "components/ErrorMessage";
import MarkdownEditor from "components/mde/MarkdownEditor";
import { useActionState } from "hooks";
const urlRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;
......@@ -21,10 +22,10 @@ const AddAnnouncementForm = ({ className }) => {
type,
});
const onTextInput = (evt) => {
setText(evt.target.value);
const onTextInput = (newText) => {
setText(newText);
if (evt.target.value !== "") {
if (newText !== "") {
setNoTextError(false);
}
};
......@@ -90,27 +91,21 @@ const AddAnnouncementForm = ({ className }) => {
</div>
</div>
<div
className={classNames("form-field", {
"form-field--error": noTextError,
})}
>
<div className="form-field__wrapper form-field__wrapper--shadowed">
<textarea
className="text-input text-sm form-field__control "
<MarkdownEditor
value={text}
rows="3"
cols="40"
placeholder="Vyplňte text oznámení"
onChange={onTextInput}
></textarea>
</div>
{noTextError && (
<div className="form-field__error">
Před přidáním oznámení nezapomeňte vyplnit jeho obsah.
</div>
)}
</div>
error={
noTextError
? "Před přidáním oznámení nezapomeňte vyplnit jeho obsah."
: null
}
placeholder="Vyplňte text oznámení"
toolbarCommands={[
["bold", "italic", "strikethrough"],
["link", "unordered-list", "ordered-list"],
]}
minEditorHeight={100}
/>
<div
className={classNames("form-field", {
......
import React, { useState } from "react";
import classNames from "classnames";
import { addPost, addProposal } from "actions/posts";
import Button from "components/Button";
import ErrorMessage from "components/ErrorMessage";
import MarkdownEditor from "components/mde/MarkdownEditor";
import { useActionState } from "hooks";
const AddPostForm = ({ className }) => {
......@@ -17,10 +17,10 @@ const AddPostForm = ({ className }) => {
content: text,
});
const onTextInput = (evt) => {
setText(evt.target.value);
const onTextInput = (newText) => {
setText(newText);
if (evt.target.value !== "") {
if (newText !== "") {
setNoTextError(false);
}
};
......@@ -83,27 +83,16 @@ const AddPostForm = ({ className }) => {
Při přidávání příspěvku došlo k problému: {addingProposalError}.
</ErrorMessage>
)}
<div
className={classNames("form-field", {
"form-field--error": noTextError,
})}
>
<div className="form-field__wrapper form-field__wrapper--shadowed">
<textarea
className="text-input form-field__control "
<MarkdownEditor
value={text}
rows="5"
cols="40"
placeholder="Vyplňte text vašeho příspěvku"
onChange={onTextInput}
></textarea>
</div>
{noTextError && (
<div className="form-field__error">
Před přidáním příspěvku nezapomeňte vyplnit jeho obsah.
</div>
)}
</div>
error={
noTextError
? "Před přidáním příspěvku nezapomeňte vyplnit jeho obsah."
: null
}
placeholder="Vyplňte text vašeho příspěvku"
/>
<div className="space-x-4">
<Button
......
......@@ -146,14 +146,7 @@ const PostsContainer = ({ className }) => {
const sliceStart = (window.page - 1) * window.perPage;
const sliceEnd = window.page * window.perPage;
let windowItems = window.items.map((postId) => items[postId]);
// Only display proposals verified by chairman to other users.
if (!user || user.role !== "chairman") {
windowItems = windowItems.filter(
(item) => item.type === "post" || item.state !== "pending"
);
}
const windowItems = window.items.map((postId) => items[postId]);
return (
<>
......
......@@ -39,6 +39,7 @@ const postStoreInitial = {
flags: "all",
sort: "byDate",
type: "all",
showPendingProposals: false,
},
};
......
......@@ -28,6 +28,12 @@ export const filterPosts = (filters, allItems) => {
let filteredItems = filter(allItems, predicate);
if (!filters.showPendingProposals) {
filteredItems = filteredItems.filter(
(item) => item.type === "post" || item.state !== "pending"
);
}
if (filters.sort === "byDate") {
return filteredItems.sort((a, b) => b.datetime - a.datetime);
}
......
......@@ -124,6 +124,7 @@ declare namespace CF2021 {
flags: "all" | "active" | "archived";
sort: "byDate" | "byScore";
type: "all" | "proposalsOnly" | "discussionOnly";
showPendingProposals: boolean;
}
export interface PostStorePayload {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment