Browse Source

fix: mac输入法冲突兼容 登录弹窗自动勾选协议

sourcemap
WIN-UGQIHHLSKBB\EDY 3 weeks ago
parent
commit
bb97e50e58
  1. 2
      src/dialog/LoginRegisterModal/index.tsx
  2. 20
      src/layouts/globalPromptPanel/composerSections/ReferenceUploadPromptSection.tsx
  3. 46
      src/pages/Voice/components/music/VoiceLyricsTextbox.tsx
  4. 21
      src/pages/Voice/components/speech/VoiceSpeechPanel.tsx
  5. 30
      src/utils/tiptapImeUtils.ts

2
src/dialog/LoginRegisterModal/index.tsx

@ -26,7 +26,7 @@ export function LoginRegisterModal({ onClose, onSelect }: LoginRegisterModalProp
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const [code, setCode] = useState(""); const [code, setCode] = useState("");
const [inviteCode, setSceneCode] = useState(""); const [inviteCode, setSceneCode] = useState("");
const [agreementChecked, setAgreementChecked] = useState(false); const [agreementChecked, setAgreementChecked] = useState(true);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const wxPollRef = useRef<number | null>(null); const wxPollRef = useRef<number | null>(null);

20
src/layouts/globalPromptPanel/composerSections/ReferenceUploadPromptSection.tsx

@ -5,6 +5,7 @@
* Tiptap / ProseMirror @主体标签是 inline atom node * Tiptap / ProseMirror @主体标签是 inline atom node
* / MIME `application/x-popiart-subjects+json` * / MIME `application/x-popiart-subjects+json`
*/ */
import { deferUntilTiptapCompositionEnd, isTiptapEditorComposing } from "@/utils/tiptapImeUtils";
import { mergeAttributes, Node as TiptapNode, type Editor } from "@tiptap/core"; import { mergeAttributes, Node as TiptapNode, type Editor } from "@tiptap/core";
import Placeholder from "@tiptap/extension-placeholder"; import Placeholder from "@tiptap/extension-placeholder";
import { NodeSelection, TextSelection } from "@tiptap/pm/state"; import { NodeSelection, TextSelection } from "@tiptap/pm/state";
@ -1137,11 +1138,20 @@ export function ReferenceUploadPromptSection(props: CommonUploadPromptSectionPro
useEffect(() => { useEffect(() => {
if (!editor) return; if (!editor) return;
const currentAst = tiptapDocToAst(editor.getJSON());
if (isPromptAstEqual(currentAst, content)) return; const syncEditorFromContent = () => {
isApplyingEditorContentRef.current = true; const currentAst = tiptapDocToAst(editor.getJSON());
editor.commands.setContent(astToTiptapDoc(content), { emitUpdate: false }); if (isPromptAstEqual(currentAst, content)) return;
isApplyingEditorContentRef.current = false; isApplyingEditorContentRef.current = true;
editor.commands.setContent(astToTiptapDoc(content), { emitUpdate: false });
isApplyingEditorContentRef.current = false;
};
if (isTiptapEditorComposing(editor)) {
return deferUntilTiptapCompositionEnd(editor, syncEditorFromContent);
}
syncEditorFromContent();
}, [content, editor]); }, [content, editor]);
useEffect(() => { useEffect(() => {

46
src/pages/Voice/components/music/VoiceLyricsTextbox.tsx

@ -1,4 +1,12 @@
import { useCallback, useLayoutEffect, useRef, useState, type ClipboardEvent, type KeyboardEvent } from "react"; import {
useCallback,
useLayoutEffect,
useRef,
useState,
type ClipboardEvent,
type CompositionEvent,
type KeyboardEvent,
} from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import type { LyricsSectionTagItem } from "./lyricsSectionTags"; import type { LyricsSectionTagItem } from "./lyricsSectionTags";
@ -23,6 +31,7 @@ export function VoiceLyricsTextbox({ value, onChange, maxLength, placeholder }:
const { t } = useTranslation(); const { t } = useTranslation();
const editorRef = useRef<HTMLDivElement>(null); const editorRef = useRef<HTMLDivElement>(null);
const isInternalUpdateRef = useRef(false); const isInternalUpdateRef = useRef(false);
const isComposingRef = useRef(false);
const pendingSlashOpenIndexRef = useRef<number | null>(null); const pendingSlashOpenIndexRef = useRef<number | null>(null);
const [popover, setPopover] = useState<LyricsSectionTagPopoverState>(null); const [popover, setPopover] = useState<LyricsSectionTagPopoverState>(null);
const [activeIndex, setActiveIndex] = useState(0); const [activeIndex, setActiveIndex] = useState(0);
@ -62,6 +71,8 @@ export function VoiceLyricsTextbox({ value, onChange, maxLength, placeholder }:
const editor = editorRef.current; const editor = editorRef.current;
if (!editor) return; if (!editor) return;
if (isComposingRef.current) return;
if (isInternalUpdateRef.current) { if (isInternalUpdateRef.current) {
isInternalUpdateRef.current = false; isInternalUpdateRef.current = false;
return; return;
@ -79,7 +90,7 @@ export function VoiceLyricsTextbox({ value, onChange, maxLength, placeholder }:
setLyricsTextboxCaretOffset(editor, Math.min(offset, value.length)); setLyricsTextboxCaretOffset(editor, Math.min(offset, value.length));
}, [value]); }, [value]);
const handleInput = useCallback(() => { const syncInputState = useCallback(() => {
const editor = editorRef.current; const editor = editorRef.current;
if (!editor) return; if (!editor) return;
@ -105,6 +116,32 @@ export function VoiceLyricsTextbox({ value, onChange, maxLength, placeholder }:
}); });
}, [commitLyrics, openPopover]); }, [commitLyrics, openPopover]);
const handleInput = useCallback(() => {
if (isComposingRef.current) {
const editor = editorRef.current;
if (editor) {
editor.dataset.empty = getLyricsTextboxPlainText(editor).length === 0 ? "true" : "false";
}
return;
}
syncInputState();
}, [syncInputState]);
const handleCompositionStart = useCallback(() => {
isComposingRef.current = true;
}, []);
const handleCompositionEnd = useCallback(
(_event: CompositionEvent<HTMLDivElement>) => {
isComposingRef.current = false;
requestAnimationFrame(() => {
syncInputState();
});
},
[syncInputState],
);
const handleSelectTag = useCallback( const handleSelectTag = useCallback(
(item: LyricsSectionTagItem) => { (item: LyricsSectionTagItem) => {
if (!popover) return; if (!popover) return;
@ -134,6 +171,7 @@ export function VoiceLyricsTextbox({ value, onChange, maxLength, placeholder }:
if ( if (
event.key === "/" && event.key === "/" &&
!event.nativeEvent.isComposing && !event.nativeEvent.isComposing &&
!isComposingRef.current &&
!event.ctrlKey && !event.ctrlKey &&
!event.metaKey && !event.metaKey &&
!event.altKey && !event.altKey &&
@ -146,6 +184,8 @@ export function VoiceLyricsTextbox({ value, onChange, maxLength, placeholder }:
if (!popover) return; if (!popover) return;
if (event.nativeEvent.isComposing || isComposingRef.current) return;
if (event.key === "Escape") { if (event.key === "Escape") {
event.preventDefault(); event.preventDefault();
closePopover(); closePopover();
@ -198,6 +238,8 @@ export function VoiceLyricsTextbox({ value, onChange, maxLength, placeholder }:
contentEditable contentEditable
suppressContentEditableWarning suppressContentEditableWarning
onInput={handleInput} onInput={handleInput}
onCompositionStart={handleCompositionStart}
onCompositionEnd={handleCompositionEnd}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
onPaste={handlePaste} onPaste={handlePaste}
/> />

21
src/pages/Voice/components/speech/VoiceSpeechPanel.tsx

@ -1,3 +1,4 @@
import { deferUntilTiptapCompositionEnd, isTiptapEditorComposing } from "@/utils/tiptapImeUtils";
import { Extension, mergeAttributes, Node as TiptapNode, type Editor } from "@tiptap/core"; import { Extension, mergeAttributes, Node as TiptapNode, type Editor } from "@tiptap/core";
import Placeholder from "@tiptap/extension-placeholder"; import Placeholder from "@tiptap/extension-placeholder";
import type { Node as ProseMirrorNode } from "@tiptap/pm/model"; import type { Node as ProseMirrorNode } from "@tiptap/pm/model";
@ -1129,6 +1130,7 @@ const VoiceSpeechPromptEditor = forwardRef<VoiceSpeechPromptEditorHandle, VoiceS
if (isApplyingEditorContentRef.current) return; if (isApplyingEditorContentRef.current) return;
const next = tiptapDocToSpeechText(currentEditor.getJSON()); const next = tiptapDocToSpeechText(currentEditor.getJSON());
if (next.length > maxLengthRef.current) { if (next.length > maxLengthRef.current) {
if (isTiptapEditorComposing(currentEditor)) return;
isApplyingEditorContentRef.current = true; isApplyingEditorContentRef.current = true;
currentEditor.commands.setContent(speechTextToTiptapDoc(lastValidValueRef.current), { emitUpdate: false }); currentEditor.commands.setContent(speechTextToTiptapDoc(lastValidValueRef.current), { emitUpdate: false });
isApplyingEditorContentRef.current = false; isApplyingEditorContentRef.current = false;
@ -1159,11 +1161,20 @@ const VoiceSpeechPromptEditor = forwardRef<VoiceSpeechPromptEditorHandle, VoiceS
isInternalUpdateRef.current = false; isInternalUpdateRef.current = false;
return; return;
} }
const current = tiptapDocToSpeechText(editor.getJSON());
if (current === value) return; const syncFromParent = () => {
isApplyingEditorContentRef.current = true; const current = tiptapDocToSpeechText(editor.getJSON());
editor.commands.setContent(speechTextToTiptapDoc(value), { emitUpdate: false }); if (current === value) return;
isApplyingEditorContentRef.current = false; isApplyingEditorContentRef.current = true;
editor.commands.setContent(speechTextToTiptapDoc(value), { emitUpdate: false });
isApplyingEditorContentRef.current = false;
};
if (isTiptapEditorComposing(editor)) {
return deferUntilTiptapCompositionEnd(editor, syncFromParent);
}
syncFromParent();
}, [editor, value]); }, [editor, value]);
useImperativeHandle( useImperativeHandle(

30
src/utils/tiptapImeUtils.ts

@ -0,0 +1,30 @@
import type { Editor } from "@tiptap/core";
export function isTiptapEditorComposing(editor: Editor | null | undefined): boolean {
return Boolean(editor?.view?.composing);
}
/** IME 组合结束后执行回调;未在组合时立即执行。返回 useEffect 清理函数。 */
export function deferUntilTiptapCompositionEnd(editor: Editor, callback: () => void): () => void {
if (!editor.view.composing) {
callback();
return () => {};
}
const dom = editor.view.dom;
let cancelled = false;
const onCompositionEnd = () => {
dom.removeEventListener("compositionend", onCompositionEnd);
if (!cancelled) {
callback();
}
};
dom.addEventListener("compositionend", onCompositionEnd);
return () => {
cancelled = true;
dom.removeEventListener("compositionend", onCompositionEnd);
};
}
Loading…
Cancel
Save