fix: sync Editor editable state with readOnly prop (#523)
The rich-text Editor (used for card descriptions) creates its Tiptap instance once via useEditor with an empty dependency array. This means the initial value of `editable: !readOnly` is captured at mount time and never updated, and the onChange/onBlur callbacks are frozen as the first-render closures. In CardPage, the description Editor mounts as soon as the card query resolves but before the permissions query resolves, so `readOnly` is `true` and onChange/onBlur are `undefined` at that moment. When permissions resolve a moment later and `canEdit` flips to `true`, the Editor never becomes editable — leaving the description stuck read-only even for workspace admins. Fix mirrors the existing pattern in PlainTextEditor.tsx: - Use refs for onChange/onBlur so the editor reads the latest values - Add a useEffect that calls editor.setEditable(!readOnly) when the readOnly prop changes Co-authored-by: Jay <CodeEngineering@pm.me>
This commit is contained in:
@@ -457,6 +457,17 @@ export default function Editor({
|
||||
}) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// useEditor is created once (empty deps below), so keep the latest callbacks
|
||||
// in refs to avoid the editor capturing stale closures on re-render.
|
||||
const onChangeRef = useRef(onChange);
|
||||
const onBlurRef = useRef(onBlur);
|
||||
useEffect(() => {
|
||||
onChangeRef.current = onChange;
|
||||
}, [onChange]);
|
||||
useEffect(() => {
|
||||
onBlurRef.current = onBlur;
|
||||
}, [onBlur]);
|
||||
|
||||
const editor = useEditor(
|
||||
{
|
||||
extensions: [
|
||||
@@ -553,7 +564,7 @@ export default function Editor({
|
||||
...(enableYouTubeEmbed ? [YouTubeNode] : []),
|
||||
],
|
||||
content,
|
||||
onUpdate: ({ editor }) => onChange?.(editor.getHTML()),
|
||||
onUpdate: ({ editor }) => onChangeRef.current?.(editor.getHTML()),
|
||||
onBlur: ({ event }) => {
|
||||
if (
|
||||
document
|
||||
@@ -563,7 +574,7 @@ export default function Editor({
|
||||
return;
|
||||
// Only trigger onBlur if the click was outside both the editor and menu
|
||||
if (!containerRef.current?.contains(event.relatedTarget as Node)) {
|
||||
onBlur?.();
|
||||
onBlurRef.current?.();
|
||||
}
|
||||
},
|
||||
editorProps: {
|
||||
@@ -587,6 +598,16 @@ export default function Editor({
|
||||
}
|
||||
}, [content, editor]);
|
||||
|
||||
// useEditor captures `readOnly` once at creation time (empty deps above), so
|
||||
// explicitly sync `editable` when the prop changes. Without this the editor
|
||||
// gets stuck read-only when `readOnly` flips from true to false after mount
|
||||
// (e.g. card permissions resolving slower than the card data on first load).
|
||||
useEffect(() => {
|
||||
if (!editor) return;
|
||||
if (editor.isEditable === !readOnly) return;
|
||||
editor.setEditable(!readOnly);
|
||||
}, [editor, readOnly]);
|
||||
|
||||
return (
|
||||
<div ref={containerRef}>
|
||||
<style jsx global>{`
|
||||
|
||||
Reference in New Issue
Block a user