import 'index.css'
import 'react-quill/dist/quill.snow.css';
import "react-mde/lib/styles/css/react-mde-all.css";
import React, { useState, useEffect } from 'react'
import { RootState } from "../../app/rootReducer"
import { useParams } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { useSelector, useDispatch } from 'react-redux'
// @ts-ignore
import renderHTML from 'react-render-html';
import ReactQuill from 'react-quill';
import ReferenceList from './ReferenceList';
import VulnerabilityList from './VulnerabilityList';
import DeleteModal from './DeleteModal'
import {
fetchArticle,
addReference,
addVulnerability,
removeReference,
removeVulnerability,
fetchAutocompleteSuggestions
} from "./ArticleSlice"
import {
Grid, Container, Button,
Form, Input, Select,
Header, Message, Dropdown
} from 'semantic-ui-react'
interface Suggestion {
description: string,
id: string
}
interface Option {
key: string,
value: string,
text: string
}
interface Reference {
id: number,
text: string
}
interface Vulnerability {
identifier: string,
id: number
}
interface FormState {
title: string,
severity: number,
summary: string,
type: string,
references: Array<Reference>,
reference: string,
vulnerability: string,
vulnerabilities: Array<Vulnerability>,
autocompleteOptions: Array<Option>
}
const Article = () => {
let { id } = useParams();
const { t } = useTranslation();
const dispatch = useDispatch()
const articleOptions = [
{ key: "article", text: t("article_options.news"), value: "article" },
{ key: "vulnerability", text: t("article_options.vulnerability"), value: "vulnerability" },
{ key: "update", text: t("article_options.update"), value: "update" }
]
const severityOptions = [
{ key: "low", text: t("severity_options.low"), value: "1" },
{ key: "medium", text: t("severity_options.medium"), value: "2" },
{ key: "high", text: t("severity_options.high"), value: "3" },
{ key: "critical", text: t("severity_options.critical"), value: "4" }
]
const { title, summary, severity, references, type, vulnerabilities } = useSelector(
(state: RootState) => state.articles.article
)
const { error, autocompleteSuggestions } = useSelector(
(state: RootState) => state.articles
)
useEffect(() => {
if (id) {
dispatch(fetchArticle(id))
}
}, [dispatch, title, summary, type, severity, id])
const [form, setForm] = useState<FormState>({
title: title,
type: type,
summary: summary,
severity: severity,
references: references,
vulnerabilities: vulnerabilities,
reference: "",
vulnerability: "",
autocompleteOptions: [] as Array<Option>
})
// Whenever the vulnerability search is updated, repopulate the dropdown options
useEffect(() => {
if (autocompleteSuggestions.length > 0) {
const options = autocompleteSuggestions.reduce((options: Array<Option>, x: Suggestion) => {
let option = {
key: x.id,
value: x.id,
text: x.id
}
options.push(option)
return options
}, [])
setForm({
...form,
autocompleteOptions: options
})
}
}, [form.vulnerability])
useEffect(() => {
setForm({ ...form, references: references })
}, [references])
useEffect(() => {
setForm({ ...form, vulnerabilities: vulnerabilities })
}, [vulnerabilities])
const handleFormChange = (event: any, { name, value }: any) => {
setForm({
...form,
[name]: value
})
}
const handleAddReference = () => {
if (form.reference) {
dispatch(addReference(form.reference))
setForm({ ...form, reference: "" })
}
}
const handleRemoveReference = (reference: Reference) => {
dispatch(removeReference(reference.id))
}
const handleAddVulnerability = () => {
if(form.vulnerability) {
dispatch(addVulnerability(form.vulnerability))
setForm({...form, vulnerability: ""})
}
}
const handleRemoveVulnerability = (vulnerability: Vulnerability) => {
dispatch(removeVulnerability(vulnerability.id))
}
const handleQuillUpdate = (value: any) => {
setForm({ ...form, summary: value })
}
const handleAutocompleteSearch = (event: any) => {
const key = event.key
if(key === "ArrowDown" || key === "ArrowUp" || key === "Enter") return
if (event.target.value !== "")
dispatch(fetchAutocompleteSuggestions(event.target.value))
setForm({...form, vulnerability: event.target.value})
}
const [preview, setPreview] = useState("")
// TODO: Cleanup or move elsewheres
useEffect(() => {
const headline = `<h3>${form.title}</h3>`
// Combine the necessary fields for the preview
let output = headline + form.summary
for (let i = 0; i < form.references.length; i++) {
output += `<a href="#">${form.references[i].text}</a><br>`
}
if (form.vulnerabilities.length > 0) {
output += "<h3>Svakheter:</h3>"
for (let i = 0; i < form.vulnerabilities.length; i++) {
output += `<a href="#">${form.vulnerabilities[i].identifier}</a><br>`
}
}
// Replace empty lines with a more sensible format
output = output.replace(/<p><br><\/p>/g, "");
setPreview(output)
}, [form])
const handleArticleDeletion = () => {
alert("Article Deleted inside callback")
}
return (
<Container>
<Grid columns={2} divided>
<Grid.Row>
<Grid.Column>
<Header as='h1'>{t("write")}</Header>
<Form error>
<Form.Field
autoComplete="off"
name="title"
value={form.title}
onChange={handleFormChange}
control={Input}
label={t('form.title')}
placeholder={t('form.title')}
/>
<Form.Select
name="type"
value={form.type}
onChange={handleFormChange}
options={articleOptions}
label={t('form.article_type')}
placeholder={t('form.article_type')}
search
/>
<Form.Input
fluid
action
label={t("form.CVE")}
placeholder={t("form.CVE")}
>
<Dropdown
name="vulnerability"
onKeyUp={handleAutocompleteSearch}
options={form.autocompleteOptions}
value={form.vulnerability}
onChange={handleFormChange}
selection
search
fluid
style={{borderRadius: "4px 0px 0px 4px"}}
/>
<Button attached="left" color="green" icon="add" content={t("form.add")} labelPosition="right" onClick={handleAddVulnerability} onKeyPress={handleAddVulnerability}/>
</Form.Input>
{error && (
<Message visible warning content="Fant ikke informasjon om gitt CVE" />
)}
<Form.Field
control={VulnerabilityList}
handleRemove={handleRemoveVulnerability}
vulnerabilities={form.vulnerabilities}
/>
<Form.Field
name="summary"
control={ReactQuill}
value={form.summary}
onChange={handleQuillUpdate}
label={{ children: t("form.summary"), htmlFor: 'form-select-control-summary' }}
modules={{
toolbar: [
['bold', 'italic', 'underline'],
[{ 'list': 'ordered' }, { 'list': 'bullet' }],
['code', 'clean']
],
}}
/>
<Form.Field
autoComplete="off"
control={Input}
name="reference"
value={form.reference}
onChange={handleFormChange}
action={
<Button labelPosition='right'
icon='add'
content={t("form.add")}
color='green'
onClick={handleAddReference}
>
</Button>
}
label={t("form.reference")}
placeholder="URL"
onKeyPress={(event: any) => { if (event.key === "Enter") { handleAddReference() } }}
/>
<Form.Field
control={ReferenceList}
references={form.references}
handleRemove={handleRemoveReference}
/>
<Form.Field
control={Select}
name="severity"
value={form.severity}
onChange={handleFormChange}
options={severityOptions}
label={{ children: t("form.severity"), htmlFor: 'form-select-control-severity' }}
placeholder={t("form.severity")}
search
searchInput={{ id: 'form-select-control-severity' }}
/>
<Form.Group>
<Form.Field
id='form-button-control-public'
control={Button}
color="green"
content={t("form.save")}
onClick={() => { alert(JSON.stringify(form)) }}
/>
<DeleteModal callback={handleArticleDeletion} />
</Form.Group>
</Form>
</Grid.Column>
<Grid.Column>
<Header as='h1'>{t("preview")}</Header>
<div>
{renderHTML(preview)}
</div>
</Grid.Column>
</Grid.Row>
</Grid>
</Container>
)
}
export default Article
Comments