Commit 4109fe32 authored by Johnny's avatar Johnny

chore(MemoEditor): enhance focus mode handling and improve editor layout

parent 595daaa1
...@@ -152,15 +152,17 @@ const Editor = forwardRef(function Editor(props: Props, ref: React.ForwardedRef< ...@@ -152,15 +152,17 @@ const Editor = forwardRef(function Editor(props: Props, ref: React.ForwardedRef<
return ( return (
<div <div
className={cn( className={cn(
"flex flex-col justify-start items-start relative w-full h-auto bg-inherit", "flex flex-col justify-start items-start relative w-full bg-inherit",
isFocusMode ? "flex-1" : EDITOR_HEIGHT.normal, // Focus mode: flex-1 to grow and fill space; Normal: h-auto with max-height
isFocusMode ? "flex-1" : `h-auto ${EDITOR_HEIGHT.normal}`,
className, className,
)} )}
> >
<textarea <textarea
className={cn( className={cn(
"w-full my-1 text-base resize-none overflow-x-hidden overflow-y-auto bg-transparent outline-none placeholder:opacity-70 whitespace-pre-wrap break-words", "w-full my-1 text-base resize-none overflow-x-hidden overflow-y-auto bg-transparent outline-none placeholder:opacity-70 whitespace-pre-wrap break-words",
isFocusMode ? `h-auto ${EDITOR_HEIGHT.focusMode.mobile} ${EDITOR_HEIGHT.focusMode.desktop}` : "h-full", // Focus mode: flex-1 h-0 to grow within flex container; Normal: h-full to fill wrapper
isFocusMode ? "flex-1 h-0" : "h-full",
)} )}
rows={1} rows={1}
placeholder={placeholder} placeholder={placeholder}
......
...@@ -29,15 +29,25 @@ export const EditorContent = forwardRef<EditorRefActions, EditorContentProps>(({ ...@@ -29,15 +29,25 @@ export const EditorContent = forwardRef<EditorRefActions, EditorContentProps>(({
dispatch(actions.setComposing(false)); dispatch(actions.setComposing(false));
}; };
const handleContentChange = (content: string) => {
dispatch(actions.updateContent(content));
};
const handlePaste = () => {
// Paste handling is managed by the Editor component internally
};
return ( return (
<div {...dragHandlers}> <div className="w-full flex flex-col flex-1" {...dragHandlers}>
<Editor <Editor
ref={ref} ref={ref}
className="memo-editor-content" className="memo-editor-content"
initialContent={state.content} initialContent={state.content}
placeholder={placeholder || ""} placeholder={placeholder || ""}
onContentChange={actions.updateContent} isFocusMode={state.ui.isFocusMode}
onPaste={() => {}} isInIME={state.ui.isComposing}
onContentChange={handleContentChange}
onPaste={handlePaste}
onCompositionStart={handleCompositionStart} onCompositionStart={handleCompositionStart}
onCompositionEnd={handleCompositionEnd} onCompositionEnd={handleCompositionEnd}
/> />
......
...@@ -11,24 +11,36 @@ interface EditorToolbarProps { ...@@ -11,24 +11,36 @@ interface EditorToolbarProps {
} }
export const EditorToolbar: FC<EditorToolbarProps> = ({ onSave, onCancel }) => { export const EditorToolbar: FC<EditorToolbarProps> = ({ onSave, onCancel }) => {
const { state, actions } = useEditorContext(); const { state, actions, dispatch } = useEditorContext();
const { valid } = validationService.canSave(state); const { valid } = validationService.canSave(state);
const isSaving = state.ui.isLoading.saving; const isSaving = state.ui.isLoading.saving;
const handleLocationChange = (location: typeof state.metadata.location) => {
dispatch(actions.setMetadata({ location }));
};
const handleToggleFocusMode = () => {
dispatch(actions.toggleFocusMode());
};
const handleVisibilityChange = (visibility: typeof state.metadata.visibility) => {
dispatch(actions.setMetadata({ visibility }));
};
return ( return (
<div className="w-full flex flex-row justify-between items-center mb-2"> <div className="w-full flex flex-row justify-between items-center mb-2">
<div className="flex flex-row justify-start items-center"> <div className="flex flex-row justify-start items-center">
<InsertMenu <InsertMenu
isUploading={state.ui.isLoading.uploading} isUploading={state.ui.isLoading.uploading}
location={state.metadata.location} location={state.metadata.location}
onLocationChange={(location) => actions.setMetadata({ location })} onLocationChange={handleLocationChange}
onToggleFocusMode={actions.toggleFocusMode} onToggleFocusMode={handleToggleFocusMode}
/> />
</div> </div>
<div className="flex flex-row justify-end items-center gap-2"> <div className="flex flex-row justify-end items-center gap-2">
<VisibilitySelector value={state.metadata.visibility} onChange={(v) => actions.setMetadata({ visibility: v })} /> <VisibilitySelector value={state.metadata.visibility} onChange={handleVisibilityChange} />
{onCancel && ( {onCancel && (
<Button variant="ghost" onClick={onCancel} disabled={isSaving}> <Button variant="ghost" onClick={onCancel} disabled={isSaving}>
......
...@@ -14,11 +14,8 @@ export const FOCUS_MODE_TOGGLE_KEY = "f"; ...@@ -14,11 +14,8 @@ export const FOCUS_MODE_TOGGLE_KEY = "f";
export const FOCUS_MODE_EXIT_KEY = "Escape"; export const FOCUS_MODE_EXIT_KEY = "Escape";
export const EDITOR_HEIGHT = { export const EDITOR_HEIGHT = {
// Max height for normal mode - focus mode uses flex-1 to grow dynamically
normal: "max-h-[50vh]", normal: "max-h-[50vh]",
focusMode: {
mobile: "min-h-[50vh]",
desktop: "md:min-h-[60vh]",
},
} as const; } as const;
export const GEOCODING = { export const GEOCODING = {
......
...@@ -12,7 +12,7 @@ import { cacheService, errorService, memoService, validationService } from "./se ...@@ -12,7 +12,7 @@ import { cacheService, errorService, memoService, validationService } from "./se
import { EditorProvider, useEditorContext } from "./state"; import { EditorProvider, useEditorContext } from "./state";
import { MemoEditorContext } from "./types"; import { MemoEditorContext } from "./types";
export interface Props { export interface MemoEditorProps {
className?: string; className?: string;
cacheKey?: string; cacheKey?: string;
placeholder?: string; placeholder?: string;
...@@ -23,7 +23,7 @@ export interface Props { ...@@ -23,7 +23,7 @@ export interface Props {
onCancel?: () => void; onCancel?: () => void;
} }
const MemoEditor = observer((props: Props) => { const MemoEditor = observer((props: MemoEditorProps) => {
const { className, cacheKey, memoName, parentMemoName, autoFocus, placeholder, onConfirm, onCancel } = props; const { className, cacheKey, memoName, parentMemoName, autoFocus, placeholder, onConfirm, onCancel } = props;
return ( return (
...@@ -42,7 +42,7 @@ const MemoEditor = observer((props: Props) => { ...@@ -42,7 +42,7 @@ const MemoEditor = observer((props: Props) => {
); );
}); });
const MemoEditorImpl: React.FC<Props> = ({ const MemoEditorImpl: React.FC<MemoEditorProps> = ({
className, className,
cacheKey, cacheKey,
memoName, memoName,
...@@ -83,8 +83,12 @@ const MemoEditorImpl: React.FC<Props> = ({ ...@@ -83,8 +83,12 @@ const MemoEditorImpl: React.FC<Props> = ({
// Focus mode management with body scroll lock // Focus mode management with body scroll lock
useFocusMode(state.ui.isFocusMode); useFocusMode(state.ui.isFocusMode);
const handleToggleFocusMode = () => {
dispatch(actions.toggleFocusMode());
};
// Keyboard shortcuts // Keyboard shortcuts
useKeyboard(editorRef, { onSave: handleSave, onToggleFocusMode: () => dispatch(actions.toggleFocusMode()) }); useKeyboard(editorRef, { onSave: handleSave, onToggleFocusMode: handleToggleFocusMode });
async function handleSave() { async function handleSave() {
const { valid, reason } = validationService.canSave(state); const { valid, reason } = validationService.canSave(state);
...@@ -93,7 +97,7 @@ const MemoEditorImpl: React.FC<Props> = ({ ...@@ -93,7 +97,7 @@ const MemoEditorImpl: React.FC<Props> = ({
return; return;
} }
actions.setLoading("saving", true); dispatch(actions.setLoading("saving", true));
try { try {
const result = await memoService.save(state, { memoName, parentMemoName }); const result = await memoService.save(state, { memoName, parentMemoName });
...@@ -108,7 +112,7 @@ const MemoEditorImpl: React.FC<Props> = ({ ...@@ -108,7 +112,7 @@ const MemoEditorImpl: React.FC<Props> = ({
cacheService.clear(cacheService.key(currentUser.name, cacheKey)); cacheService.clear(cacheService.key(currentUser.name, cacheKey));
// Reset editor state // Reset editor state
actions.reset(); dispatch(actions.reset());
// Notify parent // Notify parent
onConfirm?.(result.memoName); onConfirm?.(result.memoName);
...@@ -118,29 +122,40 @@ const MemoEditorImpl: React.FC<Props> = ({ ...@@ -118,29 +122,40 @@ const MemoEditorImpl: React.FC<Props> = ({
const message = errorService.handle(error, t); const message = errorService.handle(error, t);
toast.error(message); toast.error(message);
} finally { } finally {
actions.setLoading("saving", false); dispatch(actions.setLoading("saving", false));
} }
} }
const toggleFocusMode = () => dispatch(actions.toggleFocusMode());
return ( return (
<MemoEditorContext.Provider value={legacyContextValue}> <MemoEditorContext.Provider value={legacyContextValue}>
<FocusModeOverlay isActive={state.ui.isFocusMode} onToggle={toggleFocusMode} /> <FocusModeOverlay isActive={state.ui.isFocusMode} onToggle={handleToggleFocusMode} />
{/*
Layout structure:
- Uses justify-between to push content to top and bottom
- In focus mode: becomes fixed with specific spacing, editor grows to fill space
- In normal mode: stays relative with max-height constraint
*/}
<div <div
className={cn( className={cn(
"group relative w-full flex flex-col justify-start items-start bg-card px-4 pt-3 pb-2 rounded-lg border border-border", "group relative w-full flex flex-col justify-between items-start bg-card px-4 pt-3 pb-1 rounded-lg border border-border",
FOCUS_MODE_STYLES.transition, FOCUS_MODE_STYLES.transition,
state.ui.isFocusMode && cn(FOCUS_MODE_STYLES.container.base, FOCUS_MODE_STYLES.container.spacing), state.ui.isFocusMode && cn(FOCUS_MODE_STYLES.container.base, FOCUS_MODE_STYLES.container.spacing),
className, className,
)} )}
> >
<FocusModeExitButton isActive={state.ui.isFocusMode} onToggle={toggleFocusMode} title={t("editor.exit-focus-mode")} /> {/* Exit button is absolutely positioned in top-right corner when active */}
<FocusModeExitButton isActive={state.ui.isFocusMode} onToggle={handleToggleFocusMode} title={t("editor.exit-focus-mode")} />
{/* Editor content grows to fill available space in focus mode */}
<EditorContent ref={editorRef} placeholder={placeholder} autoFocus={autoFocus} /> <EditorContent ref={editorRef} placeholder={placeholder} autoFocus={autoFocus} />
{/* Metadata and toolbar grouped together at bottom */}
<div className="w-full flex flex-col gap-2">
<EditorMetadata /> <EditorMetadata />
<EditorToolbar onSave={handleSave} onCancel={onCancel} /> <EditorToolbar onSave={handleSave} onCancel={onCancel} />
</div> </div>
</div>
</MemoEditorContext.Provider> </MemoEditorContext.Provider>
); );
}; };
......
...@@ -179,7 +179,9 @@ const PagedMemoList = observer((props: Props) => { ...@@ -179,7 +179,9 @@ const PagedMemoList = observer((props: Props) => {
renderer={props.renderer} renderer={props.renderer}
prefixElement={ prefixElement={
<> <>
{showMemoEditor ? <MemoEditor className="mb-2" cacheKey="home-memo-editor" /> : undefined} {showMemoEditor ? (
<MemoEditor className="mb-2" cacheKey="home-memo-editor" placeholder={t("editor.any-thoughts")} />
) : undefined}
<MemoFilters /> <MemoFilters />
</> </>
} }
......
...@@ -70,7 +70,7 @@ const AttachmentList = ({ attachments, mode, onAttachmentsChange, localFiles = [ ...@@ -70,7 +70,7 @@ const AttachmentList = ({ attachments, mode, onAttachmentsChange, localFiles = [
return ( return (
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}> <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
<SortableContext items={sortableIds} strategy={verticalListSortingStrategy}> <SortableContext items={sortableIds} strategy={verticalListSortingStrategy}>
<div className="w-full flex flex-row justify-start flex-wrap gap-2 mt-2 max-h-[50vh] overflow-y-auto"> <div className="w-full flex flex-row justify-start flex-wrap gap-2 max-h-[50vh] overflow-y-auto">
{items.map((item) => ( {items.map((item) => (
<div key={item.id}> <div key={item.id}>
{/* Uploaded items are wrapped in SortableItem for drag-and-drop */} {/* Uploaded items are wrapped in SortableItem for drag-and-drop */}
......
...@@ -67,7 +67,7 @@ const RelationList = observer(({ relations, currentMemoName, mode, onRelationsCh ...@@ -67,7 +67,7 @@ const RelationList = observer(({ relations, currentMemoName, mode, onRelationsCh
} }
return ( return (
<div className="w-full flex flex-row gap-2 mt-2 flex-wrap"> <div className="w-full flex flex-row gap-2 flex-wrap">
{referencingMemos.map((memo) => ( {referencingMemos.map((memo) => (
<RelationCard <RelationCard
key={memo.name} key={memo.name}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment