fix(reply modal): restore multiline quote insertion

This commit is contained in:
plebeius
2026-02-10 15:51:58 +08:00
parent 8a192b41b4
commit db70a9148b
2 changed files with 35 additions and 7 deletions
+12 -3
View File
@@ -50,6 +50,7 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa
const { selectedText } = useSelectedTextStore(); const { selectedText } = useSelectedTextStore();
const quoteInsertRequestId = useReplyModalStore((state) => state.quoteInsertRequestId); const quoteInsertRequestId = useReplyModalStore((state) => state.quoteInsertRequestId);
const quoteInsertNumber = useReplyModalStore((state) => state.quoteInsertNumber); const quoteInsertNumber = useReplyModalStore((state) => state.quoteInsertNumber);
const quoteInsertSelectedText = useReplyModalStore((state) => state.quoteInsertSelectedText);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [lengthError, setLengthError] = useState<string | null>(null); const [lengthError, setLengthError] = useState<string | null>(null);
@@ -239,16 +240,24 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa
lastProcessedQuoteInsertRequestIdRef.current = quoteInsertRequestId; lastProcessedQuoteInsertRequestIdRef.current = quoteInsertRequestId;
const quote = `>>${quoteInsertNumber ?? '?'}`; const quote = `>>${quoteInsertNumber ?? '?'}`;
const selectedQuote = quoteInsertSelectedText?.trimEnd() || '';
const isFocused = document.activeElement === textarea; const isFocused = document.activeElement === textarea;
const rawStart = isFocused ? (textarea.selectionStart ?? textarea.value.length) : lastSelectionStartRef.current; const rawStart = isFocused ? (textarea.selectionStart ?? textarea.value.length) : lastSelectionStartRef.current;
const selectionEnd = isFocused ? (textarea.selectionEnd ?? rawStart) : lastSelectionEndRef.current; const selectionEnd = isFocused ? (textarea.selectionEnd ?? rawStart) : lastSelectionEndRef.current;
const minStart = contentPrefix.length; const minStart = contentPrefix.length;
const start = Math.max(rawStart, minStart); const start = Math.max(rawStart, minStart);
const end = Math.max(selectionEnd, minStart); const end = Math.max(selectionEnd, minStart);
const nextValue = `${textarea.value.slice(0, start)}${quote}${textarea.value.slice(end)}`; const before = textarea.value.slice(0, start);
const after = textarea.value.slice(end);
const needsLeadingNewline = before.length > minStart && !before.endsWith('\n');
let insertion = `${needsLeadingNewline ? '\n' : ''}${quote}\n`;
if (selectedQuote) {
insertion += `${selectedQuote}\n`;
}
const nextValue = `${before}${insertion}${after}`;
textarea.value = nextValue; textarea.value = nextValue;
const nextCursor = start + quote.length; const nextCursor = before.length + insertion.length;
textarea.focus(); textarea.focus();
textarea.setSelectionRange(nextCursor, nextCursor); textarea.setSelectionRange(nextCursor, nextCursor);
lastSelectionStartRef.current = nextCursor; lastSelectionStartRef.current = nextCursor;
@@ -258,7 +267,7 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa
const formattedContent = formatMarkdown(contentWithoutPrefix); const formattedContent = formatMarkdown(contentWithoutPrefix);
setPublishReplyOptions({ content: formattedContent }); setPublishReplyOptions({ content: formattedContent });
checkContentLength(formattedContent, t); checkContentLength(formattedContent, t);
}, [showReplyModal, quoteInsertRequestId, quoteInsertNumber, contentPrefix, setPublishReplyOptions, checkContentLength, t]); }, [showReplyModal, quoteInsertRequestId, quoteInsertNumber, quoteInsertSelectedText, contentPrefix, setPublishReplyOptions, checkContentLength, t]);
// on android, auto upload file to image hosting sites with open api // on android, auto upload file to image hosting sites with open api
const [isUploading, setIsUploading] = useState(false); const [isUploading, setIsUploading] = useState(false);
+23 -4
View File
@@ -11,10 +11,26 @@ interface ReplyModalState {
scrollY: number; scrollY: number;
quoteInsertRequestId: number; quoteInsertRequestId: number;
quoteInsertNumber: number | null; quoteInsertNumber: number | null;
quoteInsertSelectedText: string | null;
closeModal: () => void; closeModal: () => void;
openReplyModal: (parentCid: string, parentNumber: number | undefined, postCid: string, threadNumber: number | undefined, subplebbitAddress: string) => void; openReplyModal: (parentCid: string, parentNumber: number | undefined, postCid: string, threadNumber: number | undefined, subplebbitAddress: string) => void;
} }
const getQuotedSelection = () => {
const text = document.getSelection()?.toString();
if (!text) return '';
// Keep each selected line as 5chan greentext and normalize newlines.
const normalizedText = text.replace(/\r\n/g, '\n').replace(/\n+$/g, '');
if (!normalizedText) return '';
return normalizedText
.split('\n')
.map((line) => `>${line}`)
.join('\n');
};
const useReplyModalStore = create<ReplyModalState>((set, get) => ({ const useReplyModalStore = create<ReplyModalState>((set, get) => ({
showReplyModal: false, showReplyModal: false,
activeCid: null, activeCid: null,
@@ -25,6 +41,7 @@ const useReplyModalStore = create<ReplyModalState>((set, get) => ({
scrollY: 0, scrollY: 0,
quoteInsertRequestId: 0, quoteInsertRequestId: 0,
quoteInsertNumber: null, quoteInsertNumber: null,
quoteInsertSelectedText: null,
closeModal: () => { closeModal: () => {
// Reset selected text if you're using that store // Reset selected text if you're using that store
@@ -35,23 +52,25 @@ const useReplyModalStore = create<ReplyModalState>((set, get) => ({
parentNumber: null, parentNumber: null,
threadNumber: null, threadNumber: null,
quoteInsertNumber: null, quoteInsertNumber: null,
quoteInsertSelectedText: null,
}); });
}, },
openReplyModal: (parentCid, parentNumber, postCid, threadNumber, subplebbitAddress) => { openReplyModal: (parentCid, parentNumber, postCid, threadNumber, subplebbitAddress) => {
const quotedSelection = getQuotedSelection();
// If the reply modal is already open, insert this quote in the current textarea at caret. // If the reply modal is already open, insert this quote in the current textarea at caret.
if (get().showReplyModal) { if (get().showReplyModal) {
set((state) => ({ set((state) => ({
quoteInsertRequestId: state.quoteInsertRequestId + 1, quoteInsertRequestId: state.quoteInsertRequestId + 1,
quoteInsertNumber: parentNumber ?? null, quoteInsertNumber: parentNumber ?? null,
quoteInsertSelectedText: quotedSelection || null,
})); }));
return; return;
} }
// Get selected text if (quotedSelection) {
const text = document.getSelection()?.toString(); useSelectedTextStore.getState().setSelectedText(`${quotedSelection}\n`);
if (text) {
useSelectedTextStore.getState().setSelectedText(`>${text}\n`);
} }
// Handle mobile scrollY // Handle mobile scrollY