@@ -605,6 +605,282 @@ private void ReplaceTodoSymbol(TextPointer start, string oldSymbol, string newSy
605605 }
606606 }
607607
608+ // --- 批量操作与编号 ---
609+
610+ private List < Paragraph > GetSelectedParagraphs ( )
611+ {
612+ var list = new List < Paragraph > ( ) ;
613+ var start = rtbContent . Selection . Start ;
614+ var end = rtbContent . Selection . End ;
615+
616+ // 确保 start < end
617+ if ( start . CompareTo ( end ) > 0 ) ( start , end ) = ( end , start ) ;
618+
619+ Block current = rtbContent . Document . Blocks . FirstBlock ;
620+ while ( current != null )
621+ {
622+ // 检查交叉:Block Start <= Selection End AND Block End >= Selection Start
623+ if ( current . ContentStart . CompareTo ( end ) <= 0 && current . ContentEnd . CompareTo ( start ) >= 0 )
624+ {
625+ if ( current is Paragraph p ) list . Add ( p ) ;
626+ }
627+
628+ if ( current . ContentStart . CompareTo ( end ) > 0 ) break ;
629+
630+ current = current . NextBlock ;
631+ }
632+ return list ;
633+ }
634+
635+ private void BatchSetTodo_Click ( object sender , RoutedEventArgs e )
636+ {
637+ var paras = GetSelectedParagraphs ( ) ;
638+ if ( paras . Count == 0 ) return ;
639+
640+ rtbContent . BeginChange ( ) ;
641+ try
642+ {
643+ foreach ( var p in paras )
644+ {
645+ string text = new TextRange ( p . ContentStart , p . ContentEnd ) . Text ;
646+ if ( ! text . StartsWith ( "☐ " ) && ! text . StartsWith ( "☑ " ) )
647+ {
648+ // 确保 Paragraph 有 Inline,如果没有(空行),添加一个 Run
649+ if ( p . Inlines . Count == 0 )
650+ {
651+ p . Inlines . Add ( new Run ( "☐ " ) ) ;
652+ }
653+ else
654+ {
655+ p . Inlines . InsertBefore ( p . Inlines . FirstInline , new Run ( "☐ " ) ) ;
656+ }
657+ }
658+ }
659+ }
660+ finally
661+ {
662+ rtbContent . EndChange ( ) ;
663+ SaveState ( ) ;
664+ }
665+ }
666+
667+ private void BatchUnsetTodo_Click ( object sender , RoutedEventArgs e )
668+ {
669+ var paras = GetSelectedParagraphs ( ) ;
670+ if ( paras . Count == 0 ) return ;
671+
672+ rtbContent . BeginChange ( ) ;
673+ try
674+ {
675+ foreach ( var p in paras )
676+ {
677+ TextPointer start = p . ContentStart . GetInsertionPosition ( LogicalDirection . Forward ) ;
678+ string text = start . GetTextInRun ( LogicalDirection . Forward ) ;
679+
680+ if ( text . StartsWith ( "☐ " ) || text . StartsWith ( "☑ " ) )
681+ {
682+ TextPointer delEnd = start . GetPositionAtOffset ( 2 ) ;
683+ new TextRange ( start , delEnd ) . Text = "" ;
684+ }
685+ else if ( text . StartsWith ( "☐" ) || text . StartsWith ( "☑" ) )
686+ {
687+ TextPointer delEnd = start . GetPositionAtOffset ( 1 ) ;
688+ new TextRange ( start , delEnd ) . Text = "" ;
689+ }
690+ }
691+ }
692+ finally
693+ {
694+ rtbContent . EndChange ( ) ;
695+ SaveState ( ) ;
696+ }
697+ }
698+
699+ private void BatchCheckTodo_Click ( object sender , RoutedEventArgs e )
700+ {
701+ var paras = GetSelectedParagraphs ( ) ;
702+ if ( paras . Count == 0 ) return ;
703+
704+ rtbContent . BeginChange ( ) ;
705+ try
706+ {
707+ foreach ( var p in paras )
708+ {
709+ TextPointer start = p . ContentStart . GetInsertionPosition ( LogicalDirection . Forward ) ;
710+ string text = start . GetTextInRun ( LogicalDirection . Forward ) ;
711+
712+ if ( text . StartsWith ( "☐" ) )
713+ {
714+ TextPointer symbolEnd = start . GetPositionAtOffset ( 1 ) ;
715+ new TextRange ( start , symbolEnd ) . Text = "☑" ;
716+ }
717+ }
718+ }
719+ finally
720+ {
721+ rtbContent . EndChange ( ) ;
722+ SaveState ( ) ;
723+ }
724+ }
725+
726+ private void BatchUncheckTodo_Click ( object sender , RoutedEventArgs e )
727+ {
728+ var paras = GetSelectedParagraphs ( ) ;
729+ if ( paras . Count == 0 ) return ;
730+
731+ rtbContent . BeginChange ( ) ;
732+ try
733+ {
734+ foreach ( var p in paras )
735+ {
736+ TextPointer start = p . ContentStart . GetInsertionPosition ( LogicalDirection . Forward ) ;
737+ string text = start . GetTextInRun ( LogicalDirection . Forward ) ;
738+
739+ if ( text . StartsWith ( "☑" ) )
740+ {
741+ TextPointer symbolEnd = start . GetPositionAtOffset ( 1 ) ;
742+ new TextRange ( start , symbolEnd ) . Text = "☐" ;
743+ }
744+ }
745+ }
746+ finally
747+ {
748+ rtbContent . EndChange ( ) ;
749+ SaveState ( ) ;
750+ }
751+ }
752+
753+ private void BatchNumbering_Click ( object sender , RoutedEventArgs e )
754+ {
755+ if ( sender is MenuItem item && item . Tag is string style )
756+ {
757+ var paras = GetSelectedParagraphs ( ) ;
758+ if ( paras . Count == 0 ) return ;
759+
760+ rtbContent . BeginChange ( ) ;
761+ try
762+ {
763+ // 先尝试移除现有的编号,避免重复添加
764+ RemoveNumbering ( paras ) ;
765+
766+ int index = 1 ;
767+ foreach ( var p in paras )
768+ {
769+ string text = new TextRange ( p . ContentStart , p . ContentEnd ) . Text ;
770+ if ( string . IsNullOrWhiteSpace ( text ) ) continue ;
771+
772+ string prefix = "" ;
773+ switch ( style )
774+ {
775+ case "1." : prefix = $ "{ index } . "; break ;
776+ case "1)" : prefix = $ "{ index } ) "; break ;
777+ case "①" : prefix = $ "{ GetCircleNumber ( index ) } "; break ;
778+ case "A." : prefix = $ "{ GetAlphaNumber ( index ) } . "; break ;
779+ }
780+
781+ if ( p . Inlines . Count == 0 )
782+ p . Inlines . Add ( new Run ( prefix ) ) ;
783+ else
784+ p . Inlines . InsertBefore ( p . Inlines . FirstInline , new Run ( prefix ) ) ;
785+
786+ index ++ ;
787+ }
788+ }
789+ finally
790+ {
791+ rtbContent . EndChange ( ) ;
792+ SaveState ( ) ;
793+ }
794+ }
795+ }
796+
797+ private void BatchRemoveNumbering_Click ( object sender , RoutedEventArgs e )
798+ {
799+ var paras = GetSelectedParagraphs ( ) ;
800+ if ( paras . Count == 0 ) return ;
801+
802+ rtbContent . BeginChange ( ) ;
803+ try
804+ {
805+ RemoveNumbering ( paras ) ;
806+ }
807+ finally
808+ {
809+ rtbContent . EndChange ( ) ;
810+ SaveState ( ) ;
811+ }
812+ }
813+
814+ private void RemoveNumbering ( List < Paragraph > paras )
815+ {
816+ foreach ( var p in paras )
817+ {
818+ TextPointer start = p . ContentStart . GetInsertionPosition ( LogicalDirection . Forward ) ;
819+ string text = start . GetTextInRun ( LogicalDirection . Forward ) ;
820+
821+ // 匹配常见的编号格式
822+ // 1. 1) ① A. (1) 等
823+ // 这里使用简单的逻辑判断,移除开头的特定模式
824+
825+ int removeLen = 0 ;
826+
827+ // 检查 A. B. ...
828+ if ( text . Length >= 3 && char . IsLetter ( text [ 0 ] ) && text [ 1 ] == '.' && text [ 2 ] == ' ' )
829+ removeLen = 3 ;
830+ // 检查数字开头的 1. 10. 1) 10) ...
831+ else if ( char . IsDigit ( text [ 0 ] ) )
832+ {
833+ int i = 0 ;
834+ while ( i < text . Length && char . IsDigit ( text [ i ] ) ) i ++ ;
835+ if ( i < text . Length && ( text [ i ] == '.' || text [ i ] == ')' ) && i + 1 < text . Length && text [ i + 1 ] == ' ' )
836+ {
837+ removeLen = i + 2 ;
838+ }
839+ }
840+ // 检查 ① ... ⑳
841+ else if ( text . Length >= 2 && text [ 0 ] >= '①' && text [ 0 ] <= '⑳' && text [ 1 ] == ' ' )
842+ {
843+ removeLen = 2 ;
844+ }
845+ // 检查 (1) ...
846+ else if ( text . StartsWith ( "(" ) && text . Contains ( ")" ) && text . IndexOf ( ")" ) < 6 )
847+ {
848+ int idx = text . IndexOf ( ")" ) ;
849+ if ( idx + 1 < text . Length && text [ idx + 1 ] == ' ' )
850+ {
851+ // 确保括号里是数字
852+ bool isNum = true ;
853+ for ( int k = 1 ; k < idx ; k ++ ) if ( ! char . IsDigit ( text [ k ] ) ) { isNum = false ; break ; }
854+ if ( isNum ) removeLen = idx + 2 ;
855+ }
856+ }
857+
858+ if ( removeLen > 0 )
859+ {
860+ TextPointer delEnd = start . GetPositionAtOffset ( removeLen ) ;
861+ new TextRange ( start , delEnd ) . Text = "" ;
862+ }
863+ }
864+ }
865+
866+ private string GetCircleNumber ( int i )
867+ {
868+ if ( i >= 1 && i <= 20 ) return ( ( char ) ( '①' + i - 1 ) ) . ToString ( ) ;
869+ return $ "({ i } )";
870+ }
871+
872+ private string GetAlphaNumber ( int i )
873+ {
874+ string res = "" ;
875+ while ( i > 0 )
876+ {
877+ i -- ;
878+ res = ( char ) ( 'A' + ( i % 26 ) ) + res ;
879+ i /= 26 ;
880+ }
881+ return res ;
882+ }
883+
608884 // 右键菜单:改颜色
609885 // 标签选择变化事件
610886 private void TabList_SelectionChanged ( object sender , SelectionChangedEventArgs e )
@@ -1198,6 +1474,14 @@ private void Search_Click(object sender, RoutedEventArgs e)
11981474 NoteSearch . Show ( ) ;
11991475 }
12001476
1477+ private void About_Click ( object sender , RoutedEventArgs e )
1478+ {
1479+ var version = System . Reflection . Assembly . GetExecutingAssembly ( ) . GetName ( ) . Version ;
1480+ // 格式化为 v1.4.0 (去掉末尾的 .0 如果是 0)
1481+ string verStr = version != null ? $ "{ version . Major } .{ version . Minor } .{ version . Build } " : "1.4.0" ;
1482+ System . Windows . MessageBox . Show ( $ "StickyNote 便签\n 版本: v{ verStr } \n \n 一个简单好用的桌面便签工具。", "关于" , MessageBoxButton . OK , MessageBoxImage . Information ) ;
1483+ }
1484+
12011485 }
12021486
12031487 // --- 数据管理类 ---
0 commit comments