Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ import {
} from '~/libs/ui'
import { yupResolver } from '@hookform/resolvers/yup'
import { EnvironmentConfig } from '~/config'
import { FieldHtmlEditor } from '~/libs/shared'

import { FormAddWrapper } from '../common/FormAddWrapper'
import { FormAddTerm } from '../../models'
import { formAddTermSchema } from '../../utils'
import { useManageAddTerm, useManageAddTermProps } from '../../hooks'
import { FieldHtmlEditor } from '../common/FieldHtmlEditor'

import styles from './TermsAddForm.module.scss'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@

.expirenceWrap {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-columns: 1fr;
gap: 40px;

@include ltelg {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@ import { bind, sortBy, trim } from 'lodash'
import { toast } from 'react-toastify'
import classNames from 'classnames'

import { BaseModal, Button, IconOutline, InputDatePicker, InputSelect, InputText, InputTextarea } from '~/libs/ui'
import { BaseModal, Button, IconOutline, InputDatePicker, InputSelect, InputText } from '~/libs/ui'
import {
updateDeleteOrCreateMemberTraitAsync,
UserProfile, UserTrait,
UserTraitCategoryNames,
UserTraitIds,
} from '~/libs/core'
import { getIndustryOptionLabel, getIndustryOptionValue, INDUSTRIES_OPTIONS, InputSkillSelector } from '~/libs/shared'
import {
FieldHtmlEditor,
getIndustryOptionLabel,
getIndustryOptionValue,
INDUSTRIES_OPTIONS,
InputSkillSelector,
} from '~/libs/shared'
import { fetchSkillsByIds } from '~/libs/shared/lib/services/standard-skills'

import { WorkExpirenceCard } from '../WorkExpirenceCard'
Expand Down Expand Up @@ -200,6 +206,13 @@ const ModifyWorkExpirenceModal: FC<ModifyWorkExpirenceModalProps> = (props: Modi
})
}

function handleDescriptionChange(value: string): void {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[❗❗ security]
The handleDescriptionChange function directly updates the formValues state with the new description value. Ensure that this function is only called with sanitized input to prevent potential XSS vulnerabilities, especially since the input is rich text.

setFormValues({
...formValues,
description: value,
})
}

function handleSkillsChange(event: ChangeEvent<HTMLInputElement>): void {
const selectedSkills = (event.target as any).value || []
setFormValues({
Expand Down Expand Up @@ -500,15 +513,24 @@ const ModifyWorkExpirenceModal: FC<ModifyWorkExpirenceModalProps> = (props: Modi
onChange={bind(handleFormValueChange, this, 'currentlyWorking')}
checked={formValues.currentlyWorking as boolean}
/>
<InputTextarea
<FieldHtmlEditor
name='description'
label='Description'
placeholder='Describe your role and achievements at this company'
dirty
tabIndex={0}
onChange={bind(handleFormValueChange, this, 'description')}
onChange={handleDescriptionChange}
toolbar={`
undo redo
| formatselect
| bold italic underline strikethrough
| link
| alignleft aligncenter alignright alignjustify
| numlist bullist outdent indent
| table
| removeformat
`}
value={formValues.description as string}
rows={4}
/>
<InputSkillSelector
label='Associated Skills'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,36 +33,104 @@

.workExpirenceCardDescription {
margin-top: $sp-4;
color: $black-80;
color: $black-100;
font-size: 16px;
line-height: 24px;
font-family: $font-roboto;

:global(.body-main-normal) {
color: $black-80;
line-height: 22px;

// Style for HTML elements within rich text content
p {
margin: 0 0 $sp-2 0;
line-height: 22px;
color: $black-100;

&:last-child {
margin-bottom: 0;
}
}

ul,
ol {
margin: $sp-2 0;
padding-left: $sp-6;
}

li {
margin-bottom: $sp-1;
line-height: 22px;
}

strong {
font-weight: $font-weight-bold;
color: $black-100;
}

em {
font-style: italic;
}

u {
text-decoration: underline !important;
color: $black-100;
}

span {
color: $black-100;
}

a {
color: $blue-100;
text-decoration: none;

&:hover {
text-decoration: underline;
}
}

div {
color: $black-100;
}

table {
width: 100%;
border-collapse: collapse;
margin: $sp-2 0;
border: 1px solid $black-20;

th,
td {
border: 1px solid $black-20;
padding: $sp-2;
text-align: left;
}

th {
font-weight: $font-weight-bold;
background-color: $black-5;
}
}
}
}

.workExpirenceCardSkills {
margin-top: $sp-4;
display: flex;
color: $black-100;
font-size: 16px;
line-height: 24px;
font-family: $font-roboto;

:global(.body-main-small-bold) {
margin-bottom: $sp-2;
color: $black-100;
}

.skillsList {
display: flex;
flex-wrap: wrap;
gap: $sp-2;

.skillTag {
display: inline-block;
padding: $sp-1 $sp-2;
color: $turq-160;
border-radius: 4px;
font-size: 12px;
line-height: 16px;
}
margin-left: 4px;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable complexity */

import { FC } from 'react'
import DOMPurify from 'dompurify'
import classNames from 'classnames'
import moment from 'moment'

Expand Down Expand Up @@ -78,28 +79,53 @@ const WorkExpirenceCard: FC<WorkExpirenceCardProps> = (props: WorkExpirenceCardP
</div>
) : undefined}
</div>
{props.work.description && (
{props.work.description && !props.isModalView && (
<div className={styles.workExpirenceCardDescription}>
<p className='body-main-normal'>{props.work.description}</p>
<div
className='body-main-normal'
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(props.work.description, {
ALLOWED_ATTR: [
'href', 'target', 'rel', 'style', 'align',
'border', 'cellpadding', 'cellspacing', 'colspan',
'rowspan', 'width', 'height', 'class',
],
ALLOWED_STYLES: {
'*': {
'background-color': true,
color: true,
'font-style': true,
'font-weight': true,
'text-align': true,
'text-decoration': true,
},
},
ALLOWED_TAGS: [
'p', 'br', 'strong', 'em', 'u', 's', 'strike',
'ul', 'ol', 'li', 'a', 'div', 'span', 'table',
'thead', 'tbody', 'tfoot', 'tr', 'td', 'th',
],
} as any),
}}
/>
</div>
)}
{
props.work.associatedSkills
&& Array.isArray(props.work.associatedSkills)
&& props.work.associatedSkills.length > 0
&& props.showSkills
&& !props.isModalView
&& (
<div className={styles.workExpirenceCardSkills}>
<p className='body-main-small-bold'>Skills:</p>
<p className='body-main-small-bold'>{'Skills: '}</p>
<div className={styles.skillsList}>
{props.work.associatedSkills.map((skillId: string) => {
const skillName = props.skillNamesMap?.[skillId] || skillId
return (
<span key={skillId} className={styles.skillTag}>
{skillName}
</span>
)
})}
{props
.work
.associatedSkills
.map((skillId: string) => props.skillNamesMap?.[skillId] || skillId)
.join(', ')}
</div>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.container {
:global {
.tox-tinymce {
border-radius: 0;
border: none;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Bundled Editor.
*/
import { FC } from 'react'
import classNames from 'classnames'
import 'tinymce/tinymce'
import 'tinymce/models/dom/model.min.js'
import 'tinymce/themes/silver/theme.min.js'
import 'tinymce/icons/default/icons.min.js'
import 'tinymce/skins/ui/oxide/skin'
import 'tinymce/skins/content/default/content'
import 'tinymce/skins/ui/oxide/content'
import 'tinymce/plugins/table/plugin.min.js'
import 'tinymce/plugins/link/plugin.min.js'

import { Editor } from '@tinymce/tinymce-react'

import styles from './BundledEditor.module.scss'

export const BundledEditor: FC<any> = (props: any) => (
<div className={classNames(styles.container, props.className)}>
<Editor {...props} />
</div>
)

export default BundledEditor
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as BundledEditor } from './BundledEditor'
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { FC, FocusEvent, useEffect, useRef, useState } from 'react'

import { FormInputAutocompleteOption, InputWrapper } from '~/libs/ui'

import { BundledEditor } from './BundledEditor'

interface FieldHtmlEditorProps {
readonly className?: string
readonly autocomplete?: FormInputAutocompleteOption
readonly dirty?: boolean
readonly disabled?: boolean
readonly error?: string
readonly hideInlineErrors?: boolean
readonly hint?: string
readonly label?: string
readonly name: string
readonly onBlur?: (event: FocusEvent<HTMLTextAreaElement>) => void
readonly onChange: (event: string) => void
readonly placeholder?: string
readonly spellCheck?: boolean
readonly tabIndex?: number
readonly value?: string | number
readonly classNameWrapper?: string
readonly toolbar?: string
}

const FieldHtmlEditor: FC<FieldHtmlEditorProps> = (
props: FieldHtmlEditorProps,
) => {
const editorRef = useRef<any>(null)
const [initValue, setInitValue] = useState('')

useEffect(() => {
if (!initValue) {
setInitValue(props.value as string)
}
}, [props.value])

const defaultToolbar = 'undo redo | formatselect | bold italic underline strikethrough |'
+ ' forecolor backcolor | link | alignleft aligncenter alignright alignjustify |'
+ ' numlist bullist outdent indent | table | removeformat'

return (
<InputWrapper
{...props}
dirty={!!props.dirty}
disabled={!!props.disabled}
label={props.label || props.name}
type='textarea'
hideInlineErrors={props.hideInlineErrors}
>
<BundledEditor
onInit={function onInit(_evt: any, editor: any) {
(editorRef.current = editor)
}}
onChange={function onChange() {
props.onChange(editorRef.current.getContent())
}}
onBlur={props.onBlur}
initialValue={initValue}
init={{
browser_spellcheck: true,
content_style:
'body {'
+ 'font-family: "Roboto", Arial, Helvetica, sans-serif;'
+ 'font-size: 14px; line-height: 22px;'
+ '}',
height: 400,
menubar: false,
plugins: ['table', 'link', 'textcolor', 'contextmenu'],
source_view: true,
statusbar: false,
toolbar: props.toolbar || defaultToolbar,
}}
/>
</InputWrapper>
)
}

export default FieldHtmlEditor
Loading
Loading