Skip to content

Commit 6a847b8

Browse files
committed
refactor: added validation, fixed toolbar display when disabled, cleanup
1 parent 80fcda1 commit 6a847b8

File tree

3 files changed

+49
-96
lines changed

3 files changed

+49
-96
lines changed

src/front-end/typescript/lib/components/form-field/plate-editor.tsx

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export interface State {
2323
showHelp: boolean;
2424
validate: (value: string) => Validation<string>;
2525
child: Child;
26+
isDirty: boolean;
2627
}
2728

2829
export interface Child {
@@ -47,7 +48,8 @@ export const init: component_.base.Init<Params, State, Msg> = (params) => {
4748
errors: params.errors,
4849
showHelp: false,
4950
validate: params.validate || ((v) => ({ tag: "valid", value: v })),
50-
child: params.child
51+
child: params.child,
52+
isDirty: false
5153
},
5254
[]
5355
];
@@ -57,14 +59,16 @@ export const update: component_.base.Update<State, Msg> = ({ state, msg }) => {
5759
switch (msg.tag) {
5860
case "child":
5961
switch (msg.value.tag) {
60-
case "onChangeTextArea":
61-
return [
62-
state.set("child", {
62+
case "onChangeTextArea": {
63+
const newState = state
64+
.set("child", {
6365
...state.child,
6466
value: msg.value.value[0]
65-
}),
66-
[]
67-
];
67+
})
68+
.set("isDirty", true);
69+
// Trigger validation after content change when field is dirty
70+
return [validate(newState), []];
71+
}
6872
}
6973
}
7074
};
@@ -97,16 +101,37 @@ export function setValidate(
97101
): Immutable<State> {
98102
state = state.set("validate", validate);
99103
if (runValidation) {
104+
// Mark as dirty when running validation on existing content
105+
state = state.set("isDirty", true);
100106
state = validateState(state);
101107
}
102108
return state;
103109
}
104110

111+
function cleanPlateEditorValue(value: string): string {
112+
// Remove zero-width spaces, regular whitespace, and newlines
113+
// This handles the case where PlateJS serializes empty content as whitespace/special chars
114+
return value
115+
.replace(/\u200B|\u200C|\u200D|\uFEFF/g, "") // Remove zero-width spaces
116+
.trim(); // Remove regular whitespace and newlines
117+
}
118+
105119
export function validateState(state: Immutable<State>): Immutable<State> {
106-
const result = state.validate(state.child.value);
120+
// Only run validation if the field is dirty (user has made changes)
121+
if (!state.isDirty) {
122+
return state;
123+
}
124+
125+
const rawValue = state.child.value;
126+
const cleanedValue = cleanPlateEditorValue(rawValue);
127+
const result = state.validate(cleanedValue);
107128
return state.set("errors", result.tag === "invalid" ? result.value : []);
108129
}
109130

131+
export function validate(state: Immutable<State>): Immutable<State> {
132+
return validateState(state);
133+
}
134+
110135
export function isValid(state: Immutable<State>): boolean {
111136
return state.errors.length === 0;
112137
}
@@ -143,11 +168,11 @@ export const view: component_.base.View<ViewProps> = ({
143168
{
144169
readOnly: false
145170
},
146-
[toolbarMode]
171+
[toolbarMode]
147172
);
148173

149174
// Debounce ref for handling editor changes
150-
const updateTimeoutRef = useRef<NodeJS.Timeout | null>(null);
175+
const updateTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
151176

152177
// Initialize editor with markdown content on first load
153178
useEffect(() => {
@@ -263,7 +288,7 @@ export const view: component_.base.View<ViewProps> = ({
263288
<div
264289
className={`form-control tw-scoped ${hasErrors ? "is-invalid" : ""}`}
265290
style={{
266-
border: "1px solid #cfd4da",
291+
border: hasErrors ? "1px solid #dc3545" : "1px solid #cfd4da",
267292
borderRadius: "8px",
268293
padding: "12px",
269294
...extraChildProps.style
@@ -275,8 +300,8 @@ export const view: component_.base.View<ViewProps> = ({
275300
editor={editor}
276301
onChange={disabled ? undefined : handleEditorChange}
277302
readOnly={disabled}>
278-
<EditorContainer
279-
variant="demo"
303+
<EditorContainer
304+
variant="demo"
280305
className={cn(
281306
"h-72",
282307
disabled ? "overflow-hidden" : "overflow-y-auto"

src/front-end/typescript/lib/components/platejs/ui/fixed-toolbar.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
"use client";
22

33
import * as React from "react";
4+
import { useEditorReadOnly } from "platejs/react";
45
import { cn } from "./utils";
56

67
import { Toolbar } from "./toolbar";
78

89
export function FixedToolbar(props: React.ComponentProps<typeof Toolbar>) {
10+
const readOnly = useEditorReadOnly();
11+
12+
// Don't render the toolbar when in readOnly mode
13+
if (readOnly) {
14+
return null;
15+
}
16+
917
return (
1018
<Toolbar
1119
{...props}

src/front-end/typescript/lib/pages/opportunity/team-with-us/lib/components/form.tsx

Lines changed: 3 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as LongText from "front-end/lib/components/form-field/long-text";
66
import * as NumberField from "front-end/lib/components/form-field/number";
77
import * as RadioGroup from "front-end/lib/components/form-field/radio-group";
88
import * as PlateEditor from "front-end/lib/components/form-field/plate-editor";
9-
import * as AIEditor from "front-end/lib/components/platejs/ui/ai-chat-editor";
9+
1010
import * as Select from "front-end/lib/components/form-field/select";
1111
import * as ShortText from "front-end/lib/components/form-field/short-text";
1212
import * as TabbedForm from "front-end/lib/components/tabbed-form";
@@ -49,29 +49,8 @@ import {
4949
} from "shared/lib/validation";
5050
import * as opportunityValidation from "shared/lib/validation/opportunity/team-with-us";
5151
import * as genericValidation from "shared/lib/validation/opportunity/utility";
52+
5253
import Icon from "front-end/lib/views/icon";
53-
import { Plate, usePlateEditor } from "platejs/react";
54-
import { AIKit } from "front-end/lib/components/platejs/components/editor/plugins/ai-kit";
55-
import {
56-
Editor,
57-
EditorContainer
58-
} from "front-end/lib/components/platejs/ui/editor";
59-
import { FixedToolbarKit } from "front-end/lib/components/platejs/components/editor/plugins/fixed-toolbar-kit";
60-
import { MarkdownKit } from "front-end/lib/components/platejs/components/editor/plugins/markdown-kit";
61-
import { BaseBasicBlocksKit } from "front-end/lib/components/platejs/components/editor/plugins/basic-blocks-base-kit";
62-
import { BasicMarksKit } from "front-end/lib/components/platejs/components/editor/plugins/basic-marks-kit";
63-
import { CommentKit } from "front-end/lib/components/platejs/components/editor/plugins/comment-kit";
64-
import { ListKit } from "front-end/lib/components/platejs/components/editor/plugins/list-kit";
65-
import { BasicBlocksKit } from "front-end/lib/components/platejs/components/editor/plugins/basic-blocks-kit";
66-
import { MediaKit } from "front-end/lib/components/platejs/components/editor/plugins/media-kit";
67-
import { AutoformatKit } from "front-end/lib/components/platejs/components/editor/plugins/autoformat-kit";
68-
import { CursorOverlayKit } from "front-end/lib/components/platejs/components/editor/plugins/cursor-overlay-kit";
69-
import { DndKit } from "front-end/lib/components/platejs/components/editor/plugins/dnd-kit";
70-
import { ExitBreakKit } from "front-end/lib/components/platejs/components/editor/plugins/exit-break-kit";
71-
import { TrailingBlockPlugin } from "platejs";
72-
import { BlockPlaceholderKit } from "front-end/lib/components/platejs/components/editor/plugins/block-placeholder-kit";
73-
import { BlockMenuKit } from "front-end/lib/components/platejs/components/editor/plugins/block-menu-kit";
74-
import { EditorKit } from "front-end/lib/components/platejs/components/editor/editor-kit";
7554

7655
type RemoteOk = "yes" | "no";
7756

@@ -591,7 +570,7 @@ export function validate(state: Immutable<State>): Immutable<State> {
591570
.update("completionDate", (s) => FormField.validate(s))
592571
.update("maxBudget", (s) => FormField.validate(s))
593572
.update("resources", (s) => Resources.validate(s))
594-
.update("description", (s) => PlateEditor.validateState(s))
573+
.update("description", (s) => PlateEditor.validate(s))
595574
.update("resourceQuestions", (s) => ResourceQuestions.validate(s))
596575
.update("questionsWeight", (s) => FormField.validate(s))
597576
.update("challengeWeight", (s) => FormField.validate(s))
@@ -1522,68 +1501,9 @@ export const DescriptionView: component_.base.View<Props> = ({
15221501
const startDate = DateField.getValueAsString(state.startDate);
15231502
const completionDate = DateField.getValueAsString(state.completionDate);
15241503

1525-
// const editor = usePlateEditor({
1526-
// plugins: [
1527-
// ...AIKit,
1528-
// ...BlockMenuKit,
1529-
1530-
// // Elements
1531-
// ...BasicBlocksKit,
1532-
// // ...CodeBlockKit,
1533-
// // ...TableKit,
1534-
// // ...ToggleKit,
1535-
// // ...TocKit,
1536-
// ...MediaKit,
1537-
// // ...CalloutKit,
1538-
// // ...ColumnKit,
1539-
// // ...MathKit,
1540-
// // ...DateKit,
1541-
// // ...LinkKit,
1542-
// // ...MentionKit,
1543-
1544-
// // Marks
1545-
// ...BasicMarksKit,
1546-
// // ...FontKit,
1547-
// // Block Style
1548-
// ...ListKit,
1549-
// // ...AlignKit,
1550-
// // ...LineHeightKit,
1551-
1552-
// // Collaboration
1553-
// // ...DiscussionKit,
1554-
// ...CommentKit,
1555-
// // ...SuggestionKit,
1556-
// // Editing
1557-
// // ...SlashKit,
1558-
// ...AutoformatKit,
1559-
// ...CursorOverlayKit,
1560-
// ...DndKit,
1561-
// // ...EmojiKit,
1562-
// ...ExitBreakKit,
1563-
// TrailingBlockPlugin,
1564-
// // Parsers
1565-
// // ...DocxKit,
1566-
// ...MarkdownKit,
1567-
// // UI
1568-
// ...BlockPlaceholderKit,
1569-
// ...FixedToolbarKit,
1570-
// // ...FloatingToolbarKit,
1571-
// ],
1572-
// });
1573-
1574-
const editor = usePlateEditor({
1575-
plugins: EditorKit,
1576-
});
1577-
15781504
return (
15791505
<Row>
15801506
<Col xs="12">
1581-
{/* <Plate editor={editor}>
1582-
<EditorContainer variant="demo">
1583-
<Editor />
1584-
</EditorContainer>
1585-
</Plate> */}
1586-
15871507
<PlateEditor.view
15881508
required
15891509
label="Description and Contract Details"

0 commit comments

Comments
 (0)