|
|
@@ -58,6 +58,8 @@ export interface RichTextToolbarProps {
|
|
58
|
58
|
onAlignChange?: (align: 'left' | 'center' | 'right' | 'justify') => void;
|
|
59
|
59
|
/** 当前对齐方式(用于初始化) */
|
|
60
|
60
|
currentAlign?: 'left' | 'center' | 'right' | 'justify';
|
|
|
61
|
+ /** 基础样式(用于获取默认字号) */
|
|
|
62
|
+ baseStyle?: React.CSSProperties;
|
|
61
|
63
|
/** 表格上下文(可选) - 如果提供,则显示表格样式控制 */
|
|
62
|
64
|
tableContext?: {
|
|
63
|
65
|
block: TableBlock;
|
|
|
@@ -301,6 +303,7 @@ export const RichTextToolbar: React.FC<RichTextToolbarProps> = ({
|
|
301
|
303
|
onDelete,
|
|
302
|
304
|
onAlignChange,
|
|
303
|
305
|
currentAlign,
|
|
|
306
|
+ baseStyle,
|
|
304
|
307
|
tableContext,
|
|
305
|
308
|
}) => {
|
|
306
|
309
|
const toolbarRef = useRef<HTMLDivElement>(null);
|
|
|
@@ -442,6 +445,21 @@ export const RichTextToolbar: React.FC<RichTextToolbarProps> = ({
|
|
442
|
445
|
setTextAlign(currentAlign);
|
|
443
|
446
|
}
|
|
444
|
447
|
|
|
|
448
|
+ // 从 baseStyle 提取默认字号(作为后备值)
|
|
|
449
|
+ let defaultFontSize = 12;
|
|
|
450
|
+ if (baseStyle?.fontSize) {
|
|
|
451
|
+ const baseFontSizeStr = String(baseStyle.fontSize);
|
|
|
452
|
+ const numericValue = parseFloat(baseFontSizeStr);
|
|
|
453
|
+ if (baseFontSizeStr.endsWith('pt')) {
|
|
|
454
|
+ defaultFontSize = Math.round(numericValue);
|
|
|
455
|
+ } else if (baseFontSizeStr.endsWith('px')) {
|
|
|
456
|
+ defaultFontSize = Math.round(numericValue * 0.75);
|
|
|
457
|
+ } else if (!isNaN(numericValue) && numericValue > 0) {
|
|
|
458
|
+ // 纯数字,假设是 pt
|
|
|
459
|
+ defaultFontSize = Math.round(numericValue);
|
|
|
460
|
+ }
|
|
|
461
|
+ }
|
|
|
462
|
+
|
|
445
|
463
|
const selection = window.getSelection();
|
|
446
|
464
|
if (selection && selection.rangeCount > 0) {
|
|
447
|
465
|
const range = selection.getRangeAt(0);
|
|
|
@@ -451,24 +469,37 @@ export const RichTextToolbar: React.FC<RichTextToolbarProps> = ({
|
|
451
|
469
|
: container as HTMLElement;
|
|
452
|
470
|
|
|
453
|
471
|
if (element) {
|
|
454
|
|
- const computedStyle = window.getComputedStyle(element);
|
|
|
472
|
+ // 检测是否有显式设置的字号(在 span 标签的 style 中)
|
|
|
473
|
+ let currentFontSize = defaultFontSize;
|
|
|
474
|
+ let currentElement: HTMLElement | null = element;
|
|
455
|
475
|
|
|
456
|
|
- // 检测字号
|
|
457
|
|
- const fontSizeStr = computedStyle.fontSize;
|
|
458
|
|
- let currentFontSize = 12;
|
|
459
|
|
-
|
|
460
|
|
- if (fontSizeStr) {
|
|
461
|
|
- if (fontSizeStr.endsWith('pt')) {
|
|
462
|
|
- currentFontSize = Math.round(parseFloat(fontSizeStr));
|
|
463
|
|
- } else if (fontSizeStr.endsWith('px')) {
|
|
464
|
|
- currentFontSize = Math.round(parseFloat(fontSizeStr) * 0.75);
|
|
|
476
|
+ // 向上遍历 DOM 树,查找第一个明确设置了 fontSize 的元素
|
|
|
477
|
+ while (currentElement && currentElement.classList?.contains('rich-text-editor') === false) {
|
|
|
478
|
+ if (currentElement.style.fontSize) {
|
|
|
479
|
+ const fontSizeStr = currentElement.style.fontSize;
|
|
|
480
|
+ const numericValue = parseFloat(fontSizeStr);
|
|
|
481
|
+
|
|
|
482
|
+ if (fontSizeStr.endsWith('pt')) {
|
|
|
483
|
+ currentFontSize = Math.round(numericValue);
|
|
|
484
|
+ break;
|
|
|
485
|
+ } else if (fontSizeStr.endsWith('px')) {
|
|
|
486
|
+ // px 转 pt: 1pt = 4/3px, 所以 pt = px * 0.75
|
|
|
487
|
+ currentFontSize = Math.round(numericValue * 0.75);
|
|
|
488
|
+ break;
|
|
|
489
|
+ } else if (!isNaN(numericValue) && numericValue > 0) {
|
|
|
490
|
+ // 无单位,假设是 pt
|
|
|
491
|
+ currentFontSize = Math.round(numericValue);
|
|
|
492
|
+ break;
|
|
|
493
|
+ }
|
|
465
|
494
|
}
|
|
|
495
|
+ currentElement = currentElement.parentElement;
|
|
466
|
496
|
}
|
|
467
|
497
|
|
|
468
|
498
|
setFontSize(currentFontSize);
|
|
469
|
499
|
|
|
470
|
500
|
// 检测对齐方式(如果没有提供 currentAlign)
|
|
471
|
501
|
if (!currentAlign) {
|
|
|
502
|
+ const computedStyle = window.getComputedStyle(element);
|
|
472
|
503
|
const textAlignValue = computedStyle.textAlign;
|
|
473
|
504
|
if (textAlignValue === 'left' || textAlignValue === 'center' ||
|
|
474
|
505
|
textAlignValue === 'right' || textAlignValue === 'justify') {
|
|
|
@@ -480,6 +511,9 @@ export const RichTextToolbar: React.FC<RichTextToolbarProps> = ({
|
|
480
|
511
|
}
|
|
481
|
512
|
}
|
|
482
|
513
|
}
|
|
|
514
|
+ } else {
|
|
|
515
|
+ // 没有选区时,使用默认字号
|
|
|
516
|
+ setFontSize(defaultFontSize);
|
|
483
|
517
|
}
|
|
484
|
518
|
}
|
|
485
|
519
|
});
|
|
|
@@ -492,6 +526,7 @@ export const RichTextToolbar: React.FC<RichTextToolbarProps> = ({
|
|
492
|
526
|
isTableMode,
|
|
493
|
527
|
tableContext,
|
|
494
|
528
|
currentAlign,
|
|
|
529
|
+ baseStyle,
|
|
495
|
530
|
hasSelectedTableCells,
|
|
496
|
531
|
commonFontSize,
|
|
497
|
532
|
commonColor,
|
|
|
@@ -583,14 +618,45 @@ export const RichTextToolbar: React.FC<RichTextToolbarProps> = ({
|
|
583
|
618
|
return;
|
|
584
|
619
|
}
|
|
585
|
620
|
|
|
586
|
|
- const applied = wrapSelectionWithTag('span', { fontSize: `${value}pt` });
|
|
587
|
|
- if (!applied) {
|
|
|
621
|
+ const range = selection.getRangeAt(0);
|
|
|
622
|
+
|
|
|
623
|
+ // 情况1:有选中的文本
|
|
|
624
|
+ if (!selection.isCollapsed) {
|
|
|
625
|
+ const applied = wrapSelectionWithTag('span', { fontSize: `${value}pt` });
|
|
|
626
|
+ if (!applied) {
|
|
|
627
|
+ return;
|
|
|
628
|
+ }
|
|
|
629
|
+
|
|
|
630
|
+ setFontSize(value);
|
|
|
631
|
+ savedRangeRef.current = saveSelection();
|
|
|
632
|
+ onFormat();
|
|
588
|
633
|
return;
|
|
589
|
634
|
}
|
|
590
|
635
|
|
|
591
|
|
- setFontSize(value);
|
|
592
|
|
- savedRangeRef.current = saveSelection();
|
|
593
|
|
- onFormat();
|
|
|
636
|
+ // 情况2:没有选中文本(光标位置)
|
|
|
637
|
+ // 在光标位置创建一个带有新字号的空span,用户后续输入的文字将使用这个字号
|
|
|
638
|
+ const span = document.createElement('span');
|
|
|
639
|
+ span.style.fontSize = `${value}pt`;
|
|
|
640
|
+ span.setAttribute('data-font-size-placeholder', 'true');
|
|
|
641
|
+ // 插入一个零宽空格,确保光标可以定位到这个span中
|
|
|
642
|
+ span.textContent = '\u200B'; // 零宽空格
|
|
|
643
|
+
|
|
|
644
|
+ try {
|
|
|
645
|
+ range.insertNode(span);
|
|
|
646
|
+
|
|
|
647
|
+ // 将光标移动到span内部
|
|
|
648
|
+ const newRange = document.createRange();
|
|
|
649
|
+ newRange.setStart(span.firstChild!, 1);
|
|
|
650
|
+ newRange.collapse(true);
|
|
|
651
|
+ selection.removeAllRanges();
|
|
|
652
|
+ selection.addRange(newRange);
|
|
|
653
|
+
|
|
|
654
|
+ setFontSize(value);
|
|
|
655
|
+ savedRangeRef.current = saveSelection();
|
|
|
656
|
+ onFormat();
|
|
|
657
|
+ } catch (error) {
|
|
|
658
|
+ console.error('插入字号样式失败:', error);
|
|
|
659
|
+ }
|
|
594
|
660
|
}, [getEditorElement, isTableMode, tableContext, onFormat]);
|
|
595
|
661
|
|
|
596
|
662
|
// ── 处理颜色变化 ───────────────────────────────────────────────────────────
|