Skip to content

Commit

Permalink
Add link validation
Browse files Browse the repository at this point in the history
  • Loading branch information
queden committed Jan 29, 2022
1 parent 85e5566 commit 2a174c0
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 80 deletions.
32 changes: 24 additions & 8 deletions browser/src/components/ContributionCard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
Author,
Contribution,
ClientContribution,
PatternToDisplay,
} from "src/types/common/server-api";
import dayjs from "dayjs";
Expand All @@ -14,11 +14,12 @@ import {
import { getPatternPlaceholder } from "src/types";
import { getDisplayForAuthor } from "./SignatureContent";

import sanitizeHtml from "sanitize-html";
import parse from 'html-react-parser';
import sanitizeHtml from "sanitize-html";
import ContributionsCarousel from "./ContributionsCarousel";

interface Props {
contribution: Contribution;
contribution: ClientContribution;
hideHeader?: boolean;
isCompact?: boolean;
className?: string;
Expand All @@ -28,7 +29,7 @@ export function getFullContributionResponse({
response,
prompt,
pattern,
}: Contribution) {
}: ClientContribution) {
return (
PromptDescriptions[prompt].replace(
`{${Placeholder}}`,
Expand All @@ -45,13 +46,28 @@ export function ContributionCard({
isCompact = false,
className = "",
}: Props) {
const { author, response, prompt, pattern, createdAt } = contribution;
const { author, response, responseHtml, prompt, pattern, createdAt } = contribution;

const authorDisplay = getDisplayForAuthor(author, true);
const date = dayjs(createdAt, { utc: true });
const dateDisplay = date.format("MMM, YYYY");

const responseHtml = parse(sanitizeHtml(response));

const renderHtml = (resp: string): string | JSX.Element | JSX.Element[] => {
// Remove first p tag to prevent first text going to next line, sanitize html string
// and then convert to JSX element
return parse(
sanitizeHtml(
resp.replace(
/<p[^>]*>|<\/p[^>]*>/,
""
)
)
)
};

const input = renderHtml(
responseHtml || response
);

return (
<div
Expand All @@ -70,7 +86,7 @@ export function ContributionCard({
{replaceJSX(PromptDescriptions[prompt], {
[Placeholder]: <b>{getPatternPlaceholder(pattern, prompt)}</b>,
})}{" "}
{responseHtml}
{input}
</p>
<div className={isCompact ? "blobSingleContainer" : "blobContainer"}>
<BlobSingle
Expand Down
10 changes: 7 additions & 3 deletions browser/src/components/ContributionSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useContext, useEffect, useState } from "react";
import { descriptionText } from "../classNameConstants";
import {
Author,
Contribution,
ClientContribution,
Pattern,
PatternToDisplay,
Prompt,
Expand Down Expand Up @@ -40,6 +40,9 @@ import getMockContributions from "src/utils/getMockContributions";
import { ContributionsContext, SignaturesContext } from "src/pages/Main";
import { getContributionLink } from "src/helpers/contributions";

import { Converter } from "showdown";


enum Page {
TermsOfUse,
Contribute,
Expand Down Expand Up @@ -102,7 +105,7 @@ function PreviewCard({
prompt: Prompt;
pattern: Pattern;
}) {
const contribution: Contribution = {
const contribution: ClientContribution = {
author,
response: response || "...",
prompt,
Expand Down Expand Up @@ -352,10 +355,11 @@ export function ContributionSection() {
pattern: selectedPattern,
} as any)
);
const toMarkdownConverter = new Converter();
const newContributionId = await addContribution({
prompt: selectedPrompt,
pattern: selectedPattern,
response,
response: toMarkdownConverter.makeMarkdown(response),
walletId: user!.walletId,
});
// TODO: eliminate this and just return th actual contribution data with the response above.
Expand Down
4 changes: 4 additions & 0 deletions browser/src/components/core/Editor.css
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@
margin-top: 24px;
}

.invalidLink {
border-color: red;
}

.modal {
background-color: hsla(0, 0%, 15%, 1);
width:100%;
Expand Down
111 changes: 56 additions & 55 deletions browser/src/components/core/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ import Placeholder from "@tiptap/extension-placeholder";
import History from "@tiptap/extension-history";
import CharacterCount from "@tiptap/extension-character-count";

import { Converter } from "showdown";
import sanitizeHtml from "sanitize-html";
import isURL from "validator/lib/isURL";


import { ButtonClass } from "src/types/styles";
import { ResponseCharacterLimit } from "../ContributionSection";
import "./Editor.css";
Expand All @@ -35,20 +40,19 @@ export function Editor({
}: Props) {
const [linkInput, setLinkInput] = useState<string | null>(null);
const [displayLinkModal, setDisplayLinkModal] = useState<boolean>(false);

const sanitize = (inputHtml: string): string => {
// Remove first p tag to prevent text going to next line
return inputHtml.replace(
/<p[^>]*>|<\/p[^>]*>/,
""
);
};
const [isInvalidInput, setIsInvalidInput] = useState<boolean>(false);

const openModal = () => {
setDisplayLinkModal(true)
toggleBackgroundScrollingOnModal(false);
}

const closeModal = () => {
setDisplayLinkModal(false);
setLinkInput(null);
toggleBackgroundScrollingOnModal(true)
}

// Set Cmd/Ctrl-k shortcut
const CustomLink = Link.extend({
addKeyboardShortcuts() {
Expand Down Expand Up @@ -76,27 +80,37 @@ export function Editor({
],
onUpdate: ({ editor }) => {
onChange(
sanitize(
editor.getHTML()
)
sanitizeHtml(editor.getHTML())
);
// Okay so what we want is ContributionCard should render the
// html directly to be fast, but we should store markdown
// so we should keep onChange the same so that contribution card is
// recieve the html, but we need to find a way to persist the
// markdown, but only to the request and not the contribution card
// maybe a set markdown field? or an optional value on the AddContributionRequest
setResponseLength(editor.storage.characterCount.characters());
},
})

const setLink = (cancel: boolean) => {
if (cancel) {
// Previous url
return editor.getAttributes('link').href;
const setLink = (save: boolean) => {
setIsInvalidInput(false);
if (!save) {
closeModal();
}
var url = linkInput;

// TODO: Add Link validation

if (url === "" || url === null || url === undefined) {
//editor.chain().focus().extendMarkRange('link').unsetLink().run()
editor.chain().focus().unsetLink().run()
return
var url = null;
// If link is not null, check if it's valid and display error message otherwise.
if (!(linkInput === "" || linkInput === null || linkInput === undefined)) {
if (isURL(linkInput)) {
url = linkInput;
} else {
setIsInvalidInput(true);
return;
}
} else {
editor.chain().focus().unsetLink().run();
closeModal();
return;
}

// Add so href doesn't point to pluriverse.world/{url}
Expand All @@ -107,22 +121,9 @@ export function Editor({
url = "http://" + url;
}

// Update link
editor.chain().focus().setLink({ href: url }).run()
};

const onClickAddLink = () => {
setLink(false);
setDisplayLinkModal(false);
setLinkInput(null);
toggleBackgroundScrollingOnModal(true)
};

const onCloseModal = () => {
setLink(true);
setDisplayLinkModal(false);
setLinkInput(null);
toggleBackgroundScrollingOnModal(true);
// Set link.
editor.chain().focus().setLink({ href: url }).run();
closeModal();
};

const getPreviousLink = () => {
Expand Down Expand Up @@ -168,30 +169,30 @@ export function Editor({
}}
className={`menuItem linkIcon ${displayLinkModal ? 'shimmer' : 'white'}`}
>
<strong>🔗</strong>
<strong>🔗</strong>
</button>
</div>
</>
}
<Modal
isOpen={displayLinkModal}
<Modal
isOpen={displayLinkModal}
onAfterOpen={getPreviousLink}
className="modal"
className="modal"
overlayClassName="overlay"
onRequestClose={() => onCloseModal()}
shouldCloseOnOverlayClick={true}
>
<h3 className="text-3xl font-bold">
Add Link
Add Link
</h3>
<div>
<input
type="url"
className="linkInput"
<input
type="url"
className={`linkInput ${isInvalidInput && "invalidLink"}`}
placeholder="https://interdependence.online/declaration"
value={linkInput}
value={linkInput || ""}
onInput={e => {
setLinkInput(e.target.value)
setLinkInput((e.target.value) || "")
}}
onKeyPress={handleKeyPress}
autoFocus
Expand All @@ -201,24 +202,24 @@ export function Editor({
<div className="cancelButton">
<button
className={`${ButtonClass("blue")}`}
onClick={onCloseModal}
onClick={() => setLink(false)}
>
Cancel
Cancel
</button>
</div>
<div className="addButton">
<button
className={`${ButtonClass("blue")}`}
onClick={onClickAddLink}
onClick={() => setLink(true)}
>
Save
Save
</button>
</div>
</div>
</Modal>
<EditorContent
className="form-textarea block w-full"
editor={editor}
<EditorContent
className="form-textarea block w-full"
editor={editor}
/>
</>
)
Expand Down
27 changes: 22 additions & 5 deletions browser/src/helpers/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
AddContributionResponse,
AddContributionRequest,
GetContributionRequest,
ClientContribution,
Contribution,
GetContributionsRequest,
AddUserRequest,
Expand All @@ -13,6 +14,8 @@ import {
VerifyTwitterRequest,
} from "../types/common/server-api";

import { Converter } from "showdown";

export function withQueryParams(
url: string,
params: Record<string, string>
Expand Down Expand Up @@ -70,23 +73,28 @@ export async function addContribution(
body: request,
method: "POST",
});
console.log(`Added ${response} contribution`);
return response as AddContributionResponse;
}

export async function getContribution({
id,
}: GetContributionRequest): Promise<Contribution> {
}: GetContributionRequest): Promise<ClientContribution> {
const response = await makeRequest(`${ApiUrl}/contributions/${id}`, {
method: "GET",
});
return response as Contribution;

const mdToHtmlConverter = new Converter();
response.responseHtml = mdToHtmlConverter.makeHtml(
response.response
);

return response as ClientContribution;
}

export async function getContributions({
offset,
contributionId,
}: GetContributionsRequest): Promise<Contribution[]> {
}: GetContributionsRequest): Promise<ClientContribution[]> {
const response = await makeRequest(
withQueryParams(`${ApiUrl}/contributions`, {
offset: offset ? String(offset) : offset,
Expand All @@ -96,7 +104,16 @@ export async function getContributions({
method: "GET",
}
);
return response as Contribution[];

const mdToHtmlConverter = new Converter();
const responseWithHtml = response.map((res) => {
res.responseHtml = mdToHtmlConverter.makeHtml(
res.response
);
return res;
});

return response as ClientContribution[];
}

export async function getUser({
Expand Down
5 changes: 5 additions & 0 deletions browser/src/types/common/server-api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,15 @@ export interface Contribution {
createdAt: Date;
}

export interface ClientContribution extends Contribution {
responseHtml?: string;
}

export interface AddContributionRequest {
walletId: string;
// This should be the full text response, formatted as markdown.
response: string;
// Full text response formatted as markdown as HTML.
prompt: Prompt;
pattern: Pattern;
}
Expand Down
Loading

0 comments on commit 2a174c0

Please sign in to comment.