fix(react): address Doctor findings (#1143)

Accessibility/semantics pass aligned with React Doctor: role-based spans/divs become real buttons, role=status becomes <output>, modals use native <dialog>, author/peer flags render as <img>, and embed/challenge iframes are sandboxed. Includes review follow-ups: correct button chrome/centering/themed dialog colors and focus rings, the missing aria-label i18n keys (all languages), the Yandex image-search URL, clearer .bso setup steps, a keyboard-accessible image-search dropdown, and removal of the post-edit ('comment edited') display (mod edits unchanged).
This commit is contained in:
Tommaso Casaburi
2026-05-29 17:09:07 +07:00
committed by GitHub
parent ac95e58433
commit e082c7b428
132 changed files with 1753 additions and 1224 deletions
+38 -25
View File
@@ -97,6 +97,7 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa
const nonokoRedirectPathRef = useRef<string | null>(null);
const lastSelectionStartRef = useRef(0);
const lastSelectionEndRef = useRef(0);
const initializedReplyContentKeyRef = useRef('');
const lastProcessedQuoteInsertRequestIdRef = useRef(0);
const { selectedText } = useSelectedTextStore();
const openEmpty = useReplyModalStore((state) => state.openEmpty);
@@ -243,9 +244,12 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa
);
useEffect(() => {
const checkContentLength = checkContentLengthRef.current;
const checkPostOptions = checkPostOptionsRef.current;
return () => {
checkContentLengthRef.current.cancel();
checkPostOptionsRef.current.cancel();
checkContentLength.cancel();
checkPostOptions.cancel();
restoreBodyTextSelection();
};
}, []);
@@ -293,28 +297,37 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa
// Enable spellcheck after initial content is injected into the textarea.
useEffect(() => {
if (showReplyModal && textRef.current) {
textRef.current.spellcheck = false;
textRef.current.value = openEmpty ? selectedText || '' : `${defaultParentQuote}${selectedText || ''}`;
const len = textRef.current.value.length;
lastSelectionStartRef.current = len;
lastSelectionEndRef.current = len;
const content = textRef.current.value;
const publishContent = getContentWithOptions(content, optionsRef.current?.value || '', fortuneEntryRef, diceRollRef, postOptionsDirectoryCode);
setPublishReplyOptions({ content: publishContent });
checkContentLengthRef.current(publishContent, t);
const spellcheckTimeout = window.setTimeout(() => {
if (textRef.current) {
textRef.current.spellcheck = true;
}
}, 100);
return () => {
window.clearTimeout(spellcheckTimeout);
};
if (!showReplyModal || !textRef.current) {
initializedReplyContentKeyRef.current = '';
return;
}
}, [showReplyModal, openEmpty, defaultParentQuote, selectedText]);
const initialContent = openEmpty ? selectedText || '' : `${defaultParentQuote}${selectedText || ''}`;
const initialContentKey = `${parentCid}:${openEmpty ? 'empty' : 'quoted'}:${initialContent}`;
if (initializedReplyContentKeyRef.current === initialContentKey) {
return;
}
initializedReplyContentKeyRef.current = initialContentKey;
textRef.current.spellcheck = false;
textRef.current.value = initialContent;
const len = textRef.current.value.length;
lastSelectionStartRef.current = len;
lastSelectionEndRef.current = len;
const publishContent = getContentWithOptions(initialContent, optionsRef.current?.value || '', fortuneEntryRef, diceRollRef, postOptionsDirectoryCode);
setPublishReplyOptions({ content: publishContent });
checkContentLengthRef.current(publishContent, t);
const spellcheckTimeout = window.setTimeout(() => {
if (textRef.current) {
textRef.current.spellcheck = true;
}
}, 100);
return () => {
window.clearTimeout(spellcheckTimeout);
};
}, [showReplyModal, parentCid, openEmpty, defaultParentQuote, selectedText, postOptionsDirectoryCode, setPublishReplyOptions, t]);
useEffect(() => {
if (!showReplyModal) {
@@ -414,7 +427,7 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa
const publishContent = getContentWithOptions(nextValue, optionsRef.current?.value || '', fortuneEntryRef, diceRollRef, postOptionsDirectoryCode);
setPublishReplyOptions({ content: publishContent });
checkContentLengthRef.current(publishContent, t);
}, [showReplyModal, quoteInsertRequestId, quoteInsertNumber, quoteInsertSelectedText, setPublishReplyOptions, t]);
}, [showReplyModal, quoteInsertRequestId, quoteInsertNumber, quoteInsertSelectedText, postOptionsDirectoryCode, setPublishReplyOptions, t]);
const { isUploading, uploadedFileName, handleUpload } = useFileUpload({
onUploadComplete: (uploadedUrl: string) => {
@@ -568,7 +581,7 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa
<span className={styles.spoilerButton}>
[
<label>
<input type='checkbox' onChange={(e) => setPublishReplyOptions({ spoiler: e.target.checked })} />
<input type='checkbox' aria-label={capitalize(t('spoiler'))} onChange={(e) => setPublishReplyOptions({ spoiler: e.target.checked })} />
{capitalize(t('spoiler'))}?
</label>
]