Skip to content

Commit

Permalink
Feat: custom leading builder (#2146)
Browse files Browse the repository at this point in the history
  • Loading branch information
CatHood0 committed Aug 30, 2024
1 parent eb41dab commit 87284c8
Show file tree
Hide file tree
Showing 16 changed files with 389 additions and 169 deletions.
2 changes: 2 additions & 0 deletions lib/flutter_quill.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export 'src/document/style.dart';
export 'src/editor/editor.dart';
export 'src/editor/embed/embed_editor_builder.dart';
export 'src/editor/provider.dart';
export 'src/editor/raw_editor/builders/leading_block_builder.dart';
export 'src/editor/raw_editor/config/raw_editor_configurations.dart';
export 'src/editor/raw_editor/quill_single_child_scroll_view.dart';
export 'src/editor/raw_editor/raw_editor.dart';
Expand All @@ -30,6 +31,7 @@ export 'src/editor/style_widgets/style_widgets.dart';
export 'src/editor/widgets/cursor.dart';
export 'src/editor/widgets/default_styles.dart';
export 'src/editor/widgets/link.dart';
export 'src/editor/widgets/text/utils/text_block_utils.dart';
export 'src/editor_toolbar_controller_shared/copy_cut_service/copy_cut_service.dart';
export 'src/editor_toolbar_controller_shared/copy_cut_service/copy_cut_service_provider.dart';
export 'src/editor_toolbar_controller_shared/copy_cut_service/default_copy_cut_service.dart';
Expand Down
2 changes: 1 addition & 1 deletion lib/src/delta/delta_diff.dart
Original file line number Diff line number Diff line change
Expand Up @@ -96,5 +96,5 @@ TextDirection getDirectionOfNode(Node node, [TextDirection? currentDirection]) {
} else if (direction == Attribute.rtl) {
return TextDirection.rtl;
}
return TextDirection.ltr;
return currentDirection ?? TextDirection.ltr;
}
7 changes: 7 additions & 0 deletions lib/src/editor/config/editor_configurations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import '../../editor_toolbar_shared/config/quill_shared_configurations.dart';
import '../../toolbar/theme/quill_dialog_theme.dart';
import '../editor_builder.dart';
import '../embed/embed_editor_builder.dart';
import '../raw_editor/builders/leading_block_builder.dart';
import '../raw_editor/raw_editor.dart';
import '../widgets/default_styles.dart';
import '../widgets/delegate.dart';
Expand Down Expand Up @@ -87,10 +88,13 @@ class QuillEditorConfigurations extends Equatable {
this.scribbleAreaInsets,
this.readOnlyMouseCursor = SystemMouseCursors.text,
this.onPerformAction,
this.customLeadingBlockBuilder,
});

final QuillSharedConfigurations sharedConfigurations;

final LeadingBlockNodeBuilder? customLeadingBlockBuilder;

@Deprecated('controller will be removed in future versions.')
final QuillController? controller;

Expand Down Expand Up @@ -445,6 +449,7 @@ class QuillEditorConfigurations extends Equatable {
ContentInsertionConfiguration? contentInsertionConfiguration,
GlobalKey<EditorState>? editorKey,
TextSelectionThemeData? textSelectionThemeData,
LeadingBlockNodeBuilder? customLeadingBlockBuilder,
bool? requestKeyboardFocusOnCheckListChanged,
QuillEditorElementOptions? elementOptions,
QuillEditorBuilder? builder,
Expand All @@ -457,6 +462,8 @@ class QuillEditorConfigurations extends Equatable {
}) {
return QuillEditorConfigurations(
sharedConfigurations: sharedConfigurations ?? this.sharedConfigurations,
customLeadingBlockBuilder:
customLeadingBlockBuilder ?? this.customLeadingBlockBuilder,
// ignore: deprecated_member_use_from_same_package
controller: controller ?? this.controller,
placeholder: placeholder ?? this.placeholder,
Expand Down
2 changes: 2 additions & 0 deletions lib/src/editor/editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,8 @@ class QuillEditorState extends State<QuillEditor>
key: _editorKey,
controller: controller,
configurations: QuillRawEditorConfigurations(
customLeadingBuilder:
widget.configurations.customLeadingBlockBuilder,
focusNode: widget.focusNode,
scrollController: widget.scrollController,
scrollable: configurations.scrollable,
Expand Down
151 changes: 151 additions & 0 deletions lib/src/editor/raw_editor/builders/leading_block_builder.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import 'package:flutter/material.dart';
import '../../../document/attribute.dart';
import '../../../document/nodes/node.dart';
import '../../style_widgets/checkbox_point.dart';

typedef LeadingBlockNodeBuilder = Widget? Function(Node, LeadingConfigurations);

/// This class contains all necessary values
/// to build the leading for lists and codeblocks
///
/// If you want to customize the number point of the codeblock
/// please, take care about it, because the default
/// implementation uses the same leading of
/// ordered list to show lines with correct format
class LeadingConfigurations {
LeadingConfigurations({
required this.attribute,
required this.indentLevelCounts,
required this.count,
required this.style,
required this.width,
required this.padding,
required this.value,
required this.onCheckboxTap,
required this.attrs,
this.withDot = true,
this.index,
this.lineSize,
this.enabled,
this.uiBuilder,
});

final Attribute attribute;
final Map<String, Attribute> attrs;
final bool withDot;
final Map<int, int> indentLevelCounts;
// if is a list that contains a number as its leading then this is non null
final int? index;
final int count;
final TextStyle? style;
final double? width;
final double? padding;

// these values are used if the leading is from a check list
final QuillCheckboxBuilder? uiBuilder;
final double? lineSize;
final bool? enabled;
final bool value;
final void Function(bool) onCheckboxTap;

String? get getIndexNumberByIndent {
if (index == null) return null;
var s = index.toString();
var level = 0;
if (!attrs.containsKey(Attribute.indent.key) && indentLevelCounts.isEmpty) {
indentLevelCounts.clear();
indentLevelCounts[0] = 1;
return s;
}
if (attrs.containsKey(Attribute.indent.key)) {
level = attrs[Attribute.indent.key]!.value;
} else if (!indentLevelCounts.containsKey(0)) {
// first level but is back from previous indent level
// supposed to be "2."
indentLevelCounts[0] = 1;
}
if (indentLevelCounts.containsKey(level + 1)) {
// last visited level is done, going up
indentLevelCounts.remove(level + 1);
}
final count = (indentLevelCounts[level] ?? 0) + 1;
indentLevelCounts[level] = count;

s = count.toString();
if (level % 3 == 1) {
// a. b. c. d. e. ...
s = _toExcelSheetColumnTitle(count);
} else if (level % 3 == 2) {
// i. ii. iii. ...
s = _intToRoman(count);
}
return s;
}

String _toExcelSheetColumnTitle(int n) {
final result = StringBuffer();
while (n > 0) {
n--;
result.write(String.fromCharCode((n % 26).floor() + 97));
n = (n / 26).floor();
}

return result.toString().split('').reversed.join();
}

String _intToRoman(int input) {
var num = input;

if (num < 0) {
return '';
} else if (num == 0) {
return 'nulla';
}

final builder = StringBuffer();
for (var a = 0; a < _arabianRomanNumbers.length; a++) {
final times = (num / _arabianRomanNumbers[a])
.truncate(); // equals 1 only when arabianRomanNumbers[a] = num
// executes n times where n is the number of times you have to add
// the current roman number value to reach current num.
builder.write(_romanNumbers[a] * times);
num -= times *
_arabianRomanNumbers[
a]; // subtract previous roman number value from num
}

return builder.toString().toLowerCase();
}
}

const _arabianRomanNumbers = <int>[
1000,
900,
500,
400,
100,
90,
50,
40,
10,
9,
5,
4,
1
];

const _romanNumbers = <String>[
'M',
'CM',
'D',
'CD',
'C',
'XC',
'L',
'XL',
'X',
'IX',
'V',
'IV',
'I'
];
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import '../../../editor/widgets/default_styles.dart';
import '../../../editor/widgets/delegate.dart';
import '../../../editor/widgets/link.dart';
import '../../../toolbar/theme/quill_dialog_theme.dart';
import '../builders/leading_block_builder.dart';

@immutable
class QuillRawEditorConfigurations extends Equatable {
Expand Down Expand Up @@ -92,6 +93,7 @@ class QuillRawEditorConfigurations extends Equatable {
this.readOnlyMouseCursor = SystemMouseCursors.text,
this.magnifierConfiguration,
this.onPerformAction,
this.customLeadingBuilder,
});

/// Controls the document being edited.
Expand All @@ -103,6 +105,7 @@ class QuillRawEditorConfigurations extends Equatable {
final ScrollController scrollController;
final bool scrollable;
final double scrollBottomInset;
final LeadingBlockNodeBuilder? customLeadingBuilder;

/// Additional space around the editor contents.
final EdgeInsetsGeometry padding;
Expand Down
4 changes: 3 additions & 1 deletion lib/src/editor/raw_editor/raw_editor_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -970,7 +970,8 @@ class QuillRawEditorState extends EditorState
for (final node in doc.root.children) {
final attrs = node.style.attributes;

if (prevNodeOl && attrs[Attribute.list.key] != Attribute.ol) {
if (prevNodeOl && attrs[Attribute.list.key] != Attribute.ol ||
attrs.isEmpty) {
clearIndents = true;
}

Expand All @@ -984,6 +985,7 @@ class QuillRawEditorState extends EditorState
final editableTextBlock = EditableTextBlock(
block: node,
controller: controller,
customLeadingBlockBuilder: widget.configurations.customLeadingBuilder,
textDirection: nodeTextDirection,
scrollBottomInset: widget.configurations.scrollBottomInset,
horizontalSpacing: _getHorizontalSpacingForBlock(node, _styles),
Expand Down
71 changes: 3 additions & 68 deletions lib/src/editor/style_widgets/number_point.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import 'package:flutter/widgets.dart';

import '../../../flutter_quill.dart';
import '../widgets/text/text_block.dart';

class QuillEditorNumberPoint extends StatelessWidget {
const QuillEditorNumberPoint({
Expand All @@ -18,7 +16,7 @@ class QuillEditorNumberPoint extends StatelessWidget {
this.backgroundColor,
});

final int index;
final String index;
final Map<int?, int> indentLevelCounts;
final int count;
final TextStyle style;
Expand All @@ -31,11 +29,7 @@ class QuillEditorNumberPoint extends StatelessWidget {

@override
Widget build(BuildContext context) {
var s = index.toString();
int? level = 0;
if (!attrs.containsKey(Attribute.indent.key) && indentLevelCounts.isEmpty) {
indentLevelCounts.clear();
indentLevelCounts[0] = 1;
return Container(
alignment: AlignmentDirectional.topEnd,
width: width,
Expand All @@ -44,36 +38,12 @@ class QuillEditorNumberPoint extends StatelessWidget {
child: context.quillEditorConfigurations?.elementOptions.orderedList
.customWidget ??
Text(
withDot ? '$s.' : s,
withDot ? '$index.' : index,
style: style,
textAlign: textAlign,
),
);
}
if (attrs.containsKey(Attribute.indent.key)) {
level = attrs[Attribute.indent.key]!.value;
} else if (!indentLevelCounts.containsKey(0)) {
// first level but is back from previous indent level
// supposed to be "2."
indentLevelCounts[0] = 1;
}
if (indentLevelCounts.containsKey(level! + 1)) {
// last visited level is done, going up
indentLevelCounts.remove(level + 1);
}
final count = (indentLevelCounts[level] ?? 0) + 1;
indentLevelCounts[level] = count;

s = count.toString();
if (level % 3 == 1) {
// a. b. c. d. e. ...
s = _toExcelSheetColumnTitle(count);
} else if (level % 3 == 2) {
// i. ii. iii. ...
s = _intToRoman(count);
}
// level % 3 == 0 goes back to 1. 2. 3.

return Container(
alignment: AlignmentDirectional.topEnd,
width: width,
Expand All @@ -82,45 +52,10 @@ class QuillEditorNumberPoint extends StatelessWidget {
child: context.quillEditorConfigurations?.elementOptions.orderedList
.customWidget ??
Text(
withDot ? '$s.' : s,
withDot ? '$index.' : index,
style: style,
textAlign: textAlign,
),
);
}

String _toExcelSheetColumnTitle(int n) {
final result = StringBuffer();
while (n > 0) {
n--;
result.write(String.fromCharCode((n % 26).floor() + 97));
n = (n / 26).floor();
}

return result.toString().split('').reversed.join();
}

String _intToRoman(int input) {
var num = input;

if (num < 0) {
return '';
} else if (num == 0) {
return 'nulla';
}

final builder = StringBuffer();
for (var a = 0; a < arabianRomanNumbers.length; a++) {
final times = (num / arabianRomanNumbers[a])
.truncate(); // equals 1 only when arabianRomanNumbers[a] = num
// executes n times where n is the number of times you have to add
// the current roman number value to reach current num.
builder.write(romanNumbers[a] * times);
num -= times *
arabianRomanNumbers[
a]; // subtract previous roman number value from num
}

return builder.toString().toLowerCase();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import 'package:flutter/material.dart';
import '../../raw_editor/builders/leading_block_builder.dart';
import '../../style_widgets/style_widgets.dart';

Widget bulletPointLeading(LeadingConfigurations config) =>
QuillEditorBulletPoint(
style: config.style!,
width: config.width!,
padding: config.padding!,
);
Loading

0 comments on commit 87284c8

Please sign in to comment.