diff --git a/web/config/tailwind.config.js b/web/config/tailwind.config.js index c427613..bb50d75 100644 --- a/web/config/tailwind.config.js +++ b/web/config/tailwind.config.js @@ -141,7 +141,7 @@ export const theme = { } export const darkMode = ['class', '[data-theme="dark"]'] -export const plugins = [require('daisyui')] +export const plugins = [require('@tailwindcss/typography'), require('daisyui')] export const daisyui = { themes: [ 'light', diff --git a/web/package.json b/web/package.json index 9edb22b..8bc2ec7 100644 --- a/web/package.json +++ b/web/package.json @@ -19,6 +19,13 @@ "@redwoodjs/router": "8.3.0", "@redwoodjs/web": "8.3.0", "@redwoodjs/web-server": "8.3.0", + "@tailwindcss/typography": "^0.5.15", + "@tiptap/extension-link": "^2.8.0", + "@tiptap/extension-text-style": "^2.8.0", + "@tiptap/extension-underline": "^2.8.0", + "@tiptap/pm": "^2.8.0", + "@tiptap/react": "^2.8.0", + "@tiptap/starter-kit": "^2.8.0", "@uppy/compressor": "^2.0.1", "@uppy/core": "^4.1.0", "@uppy/dashboard": "^4.0.2", @@ -33,12 +40,14 @@ "react": "18.3.1", "react-colorful": "^5.6.1", "react-dom": "18.3.1", + "react-html-parser": "^2.0.2", "react-typed": "^2.0.12" }, "devDependencies": { "@redwoodjs/vite": "8.3.0", "@types/react": "^18.2.55", "@types/react-dom": "^18.2.19", + "@types/react-html-parser": "^2", "autoprefixer": "^10.4.20", "daisyui": "^4.12.10", "postcss": "^8.4.41", diff --git a/web/src/App.tsx b/web/src/App.tsx index cfe3925..ff6068e 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -5,7 +5,6 @@ import { AuthProvider, useAuth } from 'src/auth' import FatalErrorPage from 'src/pages/FatalErrorPage' import Routes from 'src/Routes' -import 'src/scaffold.css' import 'src/index.css' const App = () => ( diff --git a/web/src/components/Project/AdminProject/AdminProject.tsx b/web/src/components/Project/AdminProject/AdminProject.tsx index 8a2a372..38a697a 100644 --- a/web/src/components/Project/AdminProject/AdminProject.tsx +++ b/web/src/components/Project/AdminProject/AdminProject.tsx @@ -1,3 +1,4 @@ +import parseHtml from 'react-html-parser' import type { DeleteProjectMutation, DeleteProjectMutationVariables, @@ -68,7 +69,11 @@ const AdminProject = ({ project }: Props) => { Description - {project.description} + +
+ {parseHtml(project.description)} +
+ Date diff --git a/web/src/components/Project/Project/Project.tsx b/web/src/components/Project/Project/Project.tsx index 4938381..fcb5b95 100644 --- a/web/src/components/Project/Project/Project.tsx +++ b/web/src/components/Project/Project/Project.tsx @@ -1,6 +1,7 @@ import { mdiLinkVariant } from '@mdi/js' import Icon from '@mdi/react' import { format, isAfter, startOfToday } from 'date-fns' +import parseHtml from 'react-html-parser' import type { FindProjectById } from 'types/graphql' import { calculateLuminance } from 'src/lib/color' @@ -41,7 +42,9 @@ const Project = ({ project }: Props) => { ))} )} - {project.description &&

{project.description}

} + {project.description && ( +
{parseHtml(project.description)}
+ )} {project.links.length > 0 && ( <>

Links

diff --git a/web/src/components/Project/ProjectForm/ProjectForm.tsx b/web/src/components/Project/ProjectForm/ProjectForm.tsx index c98cbd5..59e4033 100644 --- a/web/src/components/Project/ProjectForm/ProjectForm.tsx +++ b/web/src/components/Project/ProjectForm/ProjectForm.tsx @@ -2,6 +2,10 @@ import { useEffect, useMemo, useRef, useState } from 'react' import { mdiCalendar, mdiDelete, mdiFormatTitle, mdiLinkVariant } from '@mdi/js' import Icon from '@mdi/react' +import Link from '@tiptap/extension-link' +import Underline from '@tiptap/extension-underline' +import { useEditor } from '@tiptap/react' +import StarterKit from '@tiptap/starter-kit' import { Meta, UploadResult } from '@uppy/core' import { format, isAfter, startOfToday } from 'date-fns' import type { @@ -11,18 +15,12 @@ import type { } from 'types/graphql' import type { RWGqlError } from '@redwoodjs/forms' -import { - Form, - FieldError, - Label, - TextField, - Submit, - TextAreaField, -} from '@redwoodjs/forms' +import { Form, FieldError, Label, TextField, Submit } from '@redwoodjs/forms' import { toast } from '@redwoodjs/web/toast' import DatePicker from 'src/components/DatePicker' import FormTextList from 'src/components/FormTextList' +import RichTextEditor from 'src/components/RichTextEditor/RichTextEditor' import TagsSelectorCell from 'src/components/Tag/TagsSelectorCell' import Uploader from 'src/components/Uploader' import { batchDelete } from 'src/lib/tus' @@ -39,6 +37,20 @@ interface ProjectFormProps { const ProjectForm = (props: ProjectFormProps) => { const today = startOfToday() + const descEditor = useEditor({ + extensions: [ + StarterKit, + Underline, + Link.configure({ + openOnClick: false, + autolink: true, + linkOnPaste: true, + defaultProtocol: 'https', + }), + ], + content: props.project?.description || '', + }) + const [links, setLinks] = useState(props.project?.links || []) const [linkErrors, setLinkErrors] = useState([]) const [pickerVisible, setPickerVisible] = useState(false) @@ -87,7 +99,7 @@ const ProjectForm = (props: ProjectFormProps) => { props.onSave( { title: data.title, - description: data.description, + description: descEditor.getHTML(), date: date.toISOString(), links: links.filter((link) => link.trim().length > 0), images: fileIds, @@ -144,21 +156,7 @@ const ProjectForm = (props: ProjectFormProps) => { - +