Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: custom leading builder #2146

Merged
merged 14 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 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/utils.dart';
import '../raw_editor/raw_editor.dart';
import '../widgets/default_styles.dart';
import '../widgets/delegate.dart';
Expand Down Expand Up @@ -87,10 +88,15 @@ class QuillEditorConfigurations extends Equatable {
this.scribbleAreaInsets,
this.readOnlyMouseCursor = SystemMouseCursors.text,
this.onPerformAction,
this.customTextLineNodeBuilder,
this.customLeadingBlockBuilder,
});

final QuillSharedConfigurations sharedConfigurations;

final TextLineNodeBuilder? customTextLineNodeBuilder;
final LeadingBlockNodeBuilder? customLeadingBlockBuilder;

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

Expand Down Expand Up @@ -445,6 +451,8 @@ class QuillEditorConfigurations extends Equatable {
ContentInsertionConfiguration? contentInsertionConfiguration,
GlobalKey<EditorState>? editorKey,
TextSelectionThemeData? textSelectionThemeData,
TextLineNodeBuilder? customTextLineNodeBuilder,
LeadingBlockNodeBuilder? customLeadingBlockBuilder,
bool? requestKeyboardFocusOnCheckListChanged,
QuillEditorElementOptions? elementOptions,
QuillEditorBuilder? builder,
Expand All @@ -458,6 +466,11 @@ class QuillEditorConfigurations extends Equatable {
return QuillEditorConfigurations(
sharedConfigurations: sharedConfigurations ?? this.sharedConfigurations,
// ignore: deprecated_member_use_from_same_package
customTextLineNodeBuilder:
customTextLineNodeBuilder ?? this.customTextLineNodeBuilder,
customLeadingBlockBuilder:
customLeadingBlockBuilder ?? this.customLeadingBlockBuilder,
// ignore: deprecated_member_use_from_same_package
controller: controller ?? this.controller,
placeholder: placeholder ?? this.placeholder,
checkBoxReadOnly: checkBoxReadOnly ?? this.checkBoxReadOnly,
Expand Down
4 changes: 4 additions & 0 deletions lib/src/editor/editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,10 @@ class QuillEditorState extends State<QuillEditor>
key: _editorKey,
controller: controller,
configurations: QuillRawEditorConfigurations(
customLeadingBuilder:
widget.configurations.customLeadingBlockBuilder,
customTextLineNodeBuilder:
widget.configurations.customTextLineNodeBuilder,
focusNode: widget.focusNode,
scrollController: widget.scrollController,
scrollable: configurations.scrollable,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import 'package:flutter/material.dart';

import '../../../common/structs/horizontal_spacing.dart';
import '../../../common/structs/vertical_spacing.dart';
import '../../../document/nodes/block.dart';
import '../../widgets/cursor.dart';
import 'config/base_builder_configuration.dart';

typedef CheckBoxTapHandler = Function(int offset, bool value);

/// TODO: implement this configurations for block lines
class BlockBuilderConfiguration extends BaseBuilderConfiguration<Block> {
BlockBuilderConfiguration({
required this.selectionColor,
required this.verticalSpacing,
required this.horizontalSpacing,
required this.cursorCont,
required this.indentLevelCounts,
required this.clearIndents,
required this.onCheckboxTap,
required this.selection,
required this.hasFocus,
required this.enableInteractiveSelection,
required this.checkBoxReadOnly,
required super.textDirection,
required super.node,
required super.customRecognizerBuilder,
required super.customStyleBuilder,
required super.customLinkPrefixes,
required super.readOnly,
required super.styles,
});
final Color selectionColor;

final VerticalSpacing verticalSpacing;
final HorizontalSpacing horizontalSpacing;
final CursorCont cursorCont;
final Map<int, int> indentLevelCounts;
final bool clearIndents;
final CheckBoxTapHandler onCheckboxTap;
final TextSelection selection;
final bool hasFocus;
final bool enableInteractiveSelection;
final bool? checkBoxReadOnly;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
import '../../../../document/nodes/node.dart';
import '../../../widgets/default_styles.dart';
import '../../../widgets/delegate.dart';

/// The base class for the configurations of the custom nodes builders
abstract class BaseBuilderConfiguration<T extends Node> {
BaseBuilderConfiguration({
required this.textDirection,
required this.node,
required this.customRecognizerBuilder,
required this.customStyleBuilder,
required this.customLinkPrefixes,
required this.readOnly,
required this.styles,
});

final TextDirection textDirection;
final T node;
final CustomRecognizerBuilder? customRecognizerBuilder;
final CustomStyleBuilder? customStyleBuilder;
final List<String> customLinkPrefixes;
final bool readOnly;
final DefaultStyles? styles;
}
141 changes: 141 additions & 0 deletions lib/src/editor/raw_editor/builders/config/leading_configurations.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import 'package:flutter/material.dart';
import '../../../../document/attribute.dart';
import '../../../style_widgets/checkbox_point.dart';

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
@@ -0,0 +1,29 @@
import '../../../document/nodes/leaf.dart';
import '../../../document/nodes/line.dart';
import '../../../document/nodes/node.dart';
import '../../embed/embed_editor_builder.dart';
import '../../widgets/link.dart';
import 'config/base_builder_configuration.dart';

typedef LaunchURL = void Function(String);
typedef LinkActionPicker = Future<LinkMenuAction> Function(Node);

class InlineBuilderConfiguration extends BaseBuilderConfiguration<Line> {
InlineBuilderConfiguration({
required super.textDirection,
required this.onLaunchUrl,
required this.linkActionPicker,
required this.embedBuilder,
required super.node,
required super.customRecognizerBuilder,
required super.customStyleBuilder,
required super.customLinkPrefixes,
required super.readOnly,
required super.styles,
required this.devicePixelRatioOf,
});
final double devicePixelRatioOf;
final LaunchURL? onLaunchUrl;
final LinkActionPicker linkActionPicker;
final EmbedBuilder Function(Embed) embedBuilder;
}
9 changes: 9 additions & 0 deletions lib/src/editor/raw_editor/builders/utils.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import 'package:flutter/material.dart';
import '../../../document/nodes/node.dart';
import '../../widgets/text/text_line.dart';
import 'config/leading_configurations.dart';
import 'inline_builder_configurations.dart';

typedef LeadingBlockNodeBuilder = Widget? Function(Node, LeadingConfigurations);
typedef TextLineNodeBuilder = Widget? Function(
Node, TextLine, InlineBuilderConfiguration);
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/utils.dart';

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

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

/// Additional space around the editor contents.
final EdgeInsetsGeometry padding;
Expand Down
Loading