First step of improving the quill editor (#1510)
* First step of improving the raw quill editorpull/1511/head
parent
39154183b7
commit
cf93061446
23 changed files with 2702 additions and 2561 deletions
@ -1,3 +0,0 @@ |
|||||||
# Contributing |
|
||||||
|
|
||||||
We welcome contributions! |
|
@ -0,0 +1,60 @@ |
|||||||
|
# Contributing |
||||||
|
|
||||||
|
The contributions are more than welcome! <br> |
||||||
|
This project will be better with the open-source community help |
||||||
|
|
||||||
|
There are no guidelines for now. |
||||||
|
This page will be updated in the future. |
||||||
|
|
||||||
|
## Steps to contributing |
||||||
|
|
||||||
|
You will need GitHub account as well as git installed and configured with your GitHub account on your machine |
||||||
|
|
||||||
|
1. Fork the repository in GitHub |
||||||
|
2. clone the forked repository using `git` |
||||||
|
3. Add the `upstream` repository using: |
||||||
|
``` |
||||||
|
git remote add upstream git@github.com:singerdmx/flutter-quill.git |
||||||
|
``` |
||||||
|
4. Open the project with your favorite IDE, we suggest using [IntelliJ IDEA Community Edition](https://www.jetbrains.com/idea/download/) |
||||||
|
5. Sync the project with Gradle |
||||||
|
6. Create a new git branch and switch to it using: |
||||||
|
|
||||||
|
``` |
||||||
|
git checkout -b your-branch-name |
||||||
|
``` |
||||||
|
The `your-branch-name` is your choice |
||||||
|
7. Make your changes |
||||||
|
8. If you are working on changes that depend on different library in the same repo, then in that directory copy `pubspec_overrides.yaml.g` which exists in all the libraries (`flutter_quill_test` and `flutter_quill_extensions` etc..) |
||||||
|
to `pubspec_overrides.yaml` which will be ignored by `.gitignore` and it will be used by dart pub to override the libraries |
||||||
|
``` |
||||||
|
cp pubspec_overrides.yaml.g pubspec_overrides.yaml |
||||||
|
``` |
||||||
|
or save some time and the following script: |
||||||
|
``` |
||||||
|
./scripts/enable_local_dev.sh |
||||||
|
``` |
||||||
|
10. Test them in the [example](../example) and add changes in there if necessary |
||||||
|
11. Mention the new changes in the [CHANGELOG.md](../CHANGELOG.md) in the next block |
||||||
|
12. Run the following script if possible |
||||||
|
``` |
||||||
|
./scripts/before-push.sh |
||||||
|
``` |
||||||
|
13. When you are done to send your pull request, run: |
||||||
|
``` |
||||||
|
git add . |
||||||
|
git commit -m "Your commit message" |
||||||
|
git push origin your-branch-name |
||||||
|
``` |
||||||
|
this will push the new branch to your forked repository |
||||||
|
14. Now you can send your pull request either by following the link that you will get in the command line or open your |
||||||
|
forked repository, and you will find an option to send the pull request, you can also |
||||||
|
open the [Pull Requests](https://github.com/singerdmx/flutter-quill) tab and send new pull request |
||||||
|
1. Please wait for the review, and we might ask you to make more changes, then run: |
||||||
|
``` |
||||||
|
git add . |
||||||
|
git commit -m "Your new commit message" |
||||||
|
git push origin your-branch-name |
||||||
|
``` |
||||||
|
|
||||||
|
Thank you for your time and efforts to this open-source community project!! |
@ -0,0 +1,3 @@ |
|||||||
|
dependency_overrides: |
||||||
|
flutter_quill: |
||||||
|
path: ../ |
@ -0,0 +1,3 @@ |
|||||||
|
dependency_overrides: |
||||||
|
flutter_quill: |
||||||
|
path: ../ |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,578 @@ |
|||||||
|
import 'package:flutter/material.dart'; |
||||||
|
|
||||||
|
import '../../models/documents/attribute.dart'; |
||||||
|
import '../editor/editor.dart'; |
||||||
|
import '../toolbar/buttons/link_style2.dart'; |
||||||
|
import '../toolbar/buttons/search/search_dialog.dart'; |
||||||
|
import 'raw_editor_state.dart'; |
||||||
|
import 'raw_editor_text_boundaries.dart'; |
||||||
|
|
||||||
|
// ------------------------------- Text Actions ------------------------------- |
||||||
|
class QuillEditorDeleteTextAction<T extends DirectionalTextEditingIntent> |
||||||
|
extends ContextAction<T> { |
||||||
|
QuillEditorDeleteTextAction(this.state, this.getTextBoundariesForIntent); |
||||||
|
|
||||||
|
final QuillRawEditorState state; |
||||||
|
final QuillEditorTextBoundary Function(T intent) getTextBoundariesForIntent; |
||||||
|
|
||||||
|
TextRange _expandNonCollapsedRange(TextEditingValue value) { |
||||||
|
final TextRange selection = value.selection; |
||||||
|
assert(selection.isValid); |
||||||
|
assert(!selection.isCollapsed); |
||||||
|
final atomicBoundary = QuillEditorCharacterBoundary(value); |
||||||
|
|
||||||
|
return TextRange( |
||||||
|
start: atomicBoundary |
||||||
|
.getLeadingTextBoundaryAt(TextPosition(offset: selection.start)) |
||||||
|
.offset, |
||||||
|
end: atomicBoundary |
||||||
|
.getTrailingTextBoundaryAt(TextPosition(offset: selection.end - 1)) |
||||||
|
.offset, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Object? invoke(T intent, [BuildContext? context]) { |
||||||
|
final selection = state.textEditingValue.selection; |
||||||
|
assert(selection.isValid); |
||||||
|
|
||||||
|
if (!selection.isCollapsed) { |
||||||
|
return Actions.invoke( |
||||||
|
context!, |
||||||
|
ReplaceTextIntent( |
||||||
|
state.textEditingValue, |
||||||
|
'', |
||||||
|
_expandNonCollapsedRange(state.textEditingValue), |
||||||
|
SelectionChangedCause.keyboard), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
final textBoundary = getTextBoundariesForIntent(intent); |
||||||
|
if (!textBoundary.textEditingValue.selection.isValid) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
if (!textBoundary.textEditingValue.selection.isCollapsed) { |
||||||
|
return Actions.invoke( |
||||||
|
context!, |
||||||
|
ReplaceTextIntent( |
||||||
|
state.textEditingValue, |
||||||
|
'', |
||||||
|
_expandNonCollapsedRange(textBoundary.textEditingValue), |
||||||
|
SelectionChangedCause.keyboard), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
return Actions.invoke( |
||||||
|
context!, |
||||||
|
ReplaceTextIntent( |
||||||
|
textBoundary.textEditingValue, |
||||||
|
'', |
||||||
|
textBoundary |
||||||
|
.getTextBoundaryAt(textBoundary.textEditingValue.selection.base), |
||||||
|
SelectionChangedCause.keyboard, |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
bool get isActionEnabled => |
||||||
|
!state.widget.readOnly && state.textEditingValue.selection.isValid; |
||||||
|
} |
||||||
|
|
||||||
|
class QuillEditorUpdateTextSelectionAction< |
||||||
|
T extends DirectionalCaretMovementIntent> extends ContextAction<T> { |
||||||
|
QuillEditorUpdateTextSelectionAction(this.state, |
||||||
|
this.ignoreNonCollapsedSelection, this.getTextBoundariesForIntent); |
||||||
|
|
||||||
|
final QuillRawEditorState state; |
||||||
|
final bool ignoreNonCollapsedSelection; |
||||||
|
final QuillEditorTextBoundary Function(T intent) getTextBoundariesForIntent; |
||||||
|
|
||||||
|
@override |
||||||
|
Object? invoke(T intent, [BuildContext? context]) { |
||||||
|
final selection = state.textEditingValue.selection; |
||||||
|
assert(selection.isValid); |
||||||
|
|
||||||
|
final collapseSelection = |
||||||
|
intent.collapseSelection || !state.widget.selectionEnabled; |
||||||
|
// Collapse to the logical start/end. |
||||||
|
TextSelection collapse(TextSelection selection) { |
||||||
|
assert(selection.isValid); |
||||||
|
assert(!selection.isCollapsed); |
||||||
|
return selection.copyWith( |
||||||
|
baseOffset: intent.forward ? selection.end : selection.start, |
||||||
|
extentOffset: intent.forward ? selection.end : selection.start, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
if (!selection.isCollapsed && |
||||||
|
!ignoreNonCollapsedSelection && |
||||||
|
collapseSelection) { |
||||||
|
return Actions.invoke( |
||||||
|
context!, |
||||||
|
UpdateSelectionIntent( |
||||||
|
state.textEditingValue, |
||||||
|
collapse(selection), |
||||||
|
SelectionChangedCause.keyboard, |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
final textBoundary = getTextBoundariesForIntent(intent); |
||||||
|
final textBoundarySelection = textBoundary.textEditingValue.selection; |
||||||
|
if (!textBoundarySelection.isValid) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
if (!textBoundarySelection.isCollapsed && |
||||||
|
!ignoreNonCollapsedSelection && |
||||||
|
collapseSelection) { |
||||||
|
return Actions.invoke( |
||||||
|
context!, |
||||||
|
UpdateSelectionIntent(state.textEditingValue, |
||||||
|
collapse(textBoundarySelection), SelectionChangedCause.keyboard), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
final extent = textBoundarySelection.extent; |
||||||
|
final newExtent = intent.forward |
||||||
|
? textBoundary.getTrailingTextBoundaryAt(extent) |
||||||
|
: textBoundary.getLeadingTextBoundaryAt(extent); |
||||||
|
|
||||||
|
final newSelection = collapseSelection |
||||||
|
? TextSelection.fromPosition(newExtent) |
||||||
|
: textBoundarySelection.extendTo(newExtent); |
||||||
|
|
||||||
|
// If collapseAtReversal is true and would have an effect, collapse it. |
||||||
|
if (!selection.isCollapsed && |
||||||
|
intent.collapseAtReversal && |
||||||
|
(selection.baseOffset < selection.extentOffset != |
||||||
|
newSelection.baseOffset < newSelection.extentOffset)) { |
||||||
|
return Actions.invoke( |
||||||
|
context!, |
||||||
|
UpdateSelectionIntent( |
||||||
|
state.textEditingValue, |
||||||
|
TextSelection.fromPosition(selection.base), |
||||||
|
SelectionChangedCause.keyboard, |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
return Actions.invoke( |
||||||
|
context!, |
||||||
|
UpdateSelectionIntent(textBoundary.textEditingValue, newSelection, |
||||||
|
SelectionChangedCause.keyboard), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
bool get isActionEnabled => state.textEditingValue.selection.isValid; |
||||||
|
} |
||||||
|
|
||||||
|
class QuillEditorExtendSelectionOrCaretPositionAction extends ContextAction< |
||||||
|
ExtendSelectionToNextWordBoundaryOrCaretLocationIntent> { |
||||||
|
QuillEditorExtendSelectionOrCaretPositionAction( |
||||||
|
this.state, this.getTextBoundariesForIntent); |
||||||
|
|
||||||
|
final QuillRawEditorState state; |
||||||
|
final QuillEditorTextBoundary Function( |
||||||
|
ExtendSelectionToNextWordBoundaryOrCaretLocationIntent intent) |
||||||
|
getTextBoundariesForIntent; |
||||||
|
|
||||||
|
@override |
||||||
|
Object? invoke(ExtendSelectionToNextWordBoundaryOrCaretLocationIntent intent, |
||||||
|
[BuildContext? context]) { |
||||||
|
final selection = state.textEditingValue.selection; |
||||||
|
assert(selection.isValid); |
||||||
|
|
||||||
|
final textBoundary = getTextBoundariesForIntent(intent); |
||||||
|
final textBoundarySelection = textBoundary.textEditingValue.selection; |
||||||
|
if (!textBoundarySelection.isValid) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
final extent = textBoundarySelection.extent; |
||||||
|
final newExtent = intent.forward |
||||||
|
? textBoundary.getTrailingTextBoundaryAt(extent) |
||||||
|
: textBoundary.getLeadingTextBoundaryAt(extent); |
||||||
|
|
||||||
|
final newSelection = (newExtent.offset - textBoundarySelection.baseOffset) * |
||||||
|
(textBoundarySelection.extentOffset - |
||||||
|
textBoundarySelection.baseOffset) < |
||||||
|
0 |
||||||
|
? textBoundarySelection.copyWith( |
||||||
|
extentOffset: textBoundarySelection.baseOffset, |
||||||
|
affinity: textBoundarySelection.extentOffset > |
||||||
|
textBoundarySelection.baseOffset |
||||||
|
? TextAffinity.downstream |
||||||
|
: TextAffinity.upstream, |
||||||
|
) |
||||||
|
: textBoundarySelection.extendTo(newExtent); |
||||||
|
|
||||||
|
return Actions.invoke( |
||||||
|
context!, |
||||||
|
UpdateSelectionIntent(textBoundary.textEditingValue, newSelection, |
||||||
|
SelectionChangedCause.keyboard), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
bool get isActionEnabled => |
||||||
|
state.widget.selectionEnabled && state.textEditingValue.selection.isValid; |
||||||
|
} |
||||||
|
|
||||||
|
class QuillEditorUpdateTextSelectionToAdjacentLineAction< |
||||||
|
T extends DirectionalCaretMovementIntent> extends ContextAction<T> { |
||||||
|
QuillEditorUpdateTextSelectionToAdjacentLineAction(this.state); |
||||||
|
|
||||||
|
final QuillRawEditorState state; |
||||||
|
|
||||||
|
QuillVerticalCaretMovementRun? _verticalMovementRun; |
||||||
|
TextSelection? _runSelection; |
||||||
|
|
||||||
|
void stopCurrentVerticalRunIfSelectionChanges() { |
||||||
|
final runSelection = _runSelection; |
||||||
|
if (runSelection == null) { |
||||||
|
assert(_verticalMovementRun == null); |
||||||
|
return; |
||||||
|
} |
||||||
|
_runSelection = state.textEditingValue.selection; |
||||||
|
final currentSelection = state.controller.selection; |
||||||
|
final continueCurrentRun = currentSelection.isValid && |
||||||
|
currentSelection.isCollapsed && |
||||||
|
currentSelection.baseOffset == runSelection.baseOffset && |
||||||
|
currentSelection.extentOffset == runSelection.extentOffset; |
||||||
|
if (!continueCurrentRun) { |
||||||
|
_verticalMovementRun = null; |
||||||
|
_runSelection = null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void invoke(T intent, [BuildContext? context]) { |
||||||
|
assert(state.textEditingValue.selection.isValid); |
||||||
|
|
||||||
|
final collapseSelection = |
||||||
|
intent.collapseSelection || !state.widget.selectionEnabled; |
||||||
|
final value = state.textEditingValue; |
||||||
|
if (!value.selection.isValid) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
final currentRun = _verticalMovementRun ?? |
||||||
|
state.renderEditor |
||||||
|
.startVerticalCaretMovement(state.renderEditor.selection.extent); |
||||||
|
|
||||||
|
final shouldMove = |
||||||
|
intent.forward ? currentRun.moveNext() : currentRun.movePrevious(); |
||||||
|
final newExtent = shouldMove |
||||||
|
? currentRun.current |
||||||
|
: (intent.forward |
||||||
|
? TextPosition(offset: state.textEditingValue.text.length) |
||||||
|
: const TextPosition(offset: 0)); |
||||||
|
final newSelection = collapseSelection |
||||||
|
? TextSelection.fromPosition(newExtent) |
||||||
|
: value.selection.extendTo(newExtent); |
||||||
|
|
||||||
|
Actions.invoke( |
||||||
|
context!, |
||||||
|
UpdateSelectionIntent( |
||||||
|
value, newSelection, SelectionChangedCause.keyboard), |
||||||
|
); |
||||||
|
if (state.textEditingValue.selection == newSelection) { |
||||||
|
_verticalMovementRun = currentRun; |
||||||
|
_runSelection = newSelection; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
bool get isActionEnabled => state.textEditingValue.selection.isValid; |
||||||
|
} |
||||||
|
|
||||||
|
class QuillEditorSelectAllAction extends ContextAction<SelectAllTextIntent> { |
||||||
|
QuillEditorSelectAllAction(this.state); |
||||||
|
|
||||||
|
final QuillRawEditorState state; |
||||||
|
|
||||||
|
@override |
||||||
|
Object? invoke(SelectAllTextIntent intent, [BuildContext? context]) { |
||||||
|
return Actions.invoke( |
||||||
|
context!, |
||||||
|
UpdateSelectionIntent( |
||||||
|
state.textEditingValue, |
||||||
|
TextSelection( |
||||||
|
baseOffset: 0, extentOffset: state.textEditingValue.text.length), |
||||||
|
intent.cause, |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
bool get isActionEnabled => state.widget.selectionEnabled; |
||||||
|
} |
||||||
|
|
||||||
|
class QuillEditorCopySelectionAction |
||||||
|
extends ContextAction<CopySelectionTextIntent> { |
||||||
|
QuillEditorCopySelectionAction(this.state); |
||||||
|
|
||||||
|
final QuillRawEditorState state; |
||||||
|
|
||||||
|
@override |
||||||
|
void invoke(CopySelectionTextIntent intent, [BuildContext? context]) { |
||||||
|
if (intent.collapseSelection) { |
||||||
|
state.cutSelection(intent.cause); |
||||||
|
} else { |
||||||
|
state.copySelection(intent.cause); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
bool get isActionEnabled => |
||||||
|
state.textEditingValue.selection.isValid && |
||||||
|
!state.textEditingValue.selection.isCollapsed; |
||||||
|
} |
||||||
|
|
||||||
|
//Intent class for "escape" key to dismiss selection toolbar in Windows platform |
||||||
|
class HideSelectionToolbarIntent extends Intent { |
||||||
|
const HideSelectionToolbarIntent(); |
||||||
|
} |
||||||
|
|
||||||
|
class QuillEditorHideSelectionToolbarAction |
||||||
|
extends ContextAction<HideSelectionToolbarIntent> { |
||||||
|
QuillEditorHideSelectionToolbarAction(this.state); |
||||||
|
|
||||||
|
final QuillRawEditorState state; |
||||||
|
|
||||||
|
@override |
||||||
|
void invoke(HideSelectionToolbarIntent intent, [BuildContext? context]) { |
||||||
|
state.hideToolbar(); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
bool get isActionEnabled => state.textEditingValue.selection.isValid; |
||||||
|
} |
||||||
|
|
||||||
|
class QuillEditorUndoKeyboardAction extends ContextAction<UndoTextIntent> { |
||||||
|
QuillEditorUndoKeyboardAction(this.state); |
||||||
|
|
||||||
|
final QuillRawEditorState state; |
||||||
|
|
||||||
|
@override |
||||||
|
void invoke(UndoTextIntent intent, [BuildContext? context]) { |
||||||
|
if (state.controller.hasUndo) { |
||||||
|
state.controller.undo(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
bool get isActionEnabled => true; |
||||||
|
} |
||||||
|
|
||||||
|
class QuillEditorRedoKeyboardAction extends ContextAction<RedoTextIntent> { |
||||||
|
QuillEditorRedoKeyboardAction(this.state); |
||||||
|
|
||||||
|
final QuillRawEditorState state; |
||||||
|
|
||||||
|
@override |
||||||
|
void invoke(RedoTextIntent intent, [BuildContext? context]) { |
||||||
|
if (state.controller.hasRedo) { |
||||||
|
state.controller.redo(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
bool get isActionEnabled => true; |
||||||
|
} |
||||||
|
|
||||||
|
class ToggleTextStyleIntent extends Intent { |
||||||
|
const ToggleTextStyleIntent(this.attribute); |
||||||
|
|
||||||
|
final Attribute attribute; |
||||||
|
} |
||||||
|
|
||||||
|
// Toggles a text style (underline, bold, italic, strikethrough) on, or off. |
||||||
|
class QuillEditorToggleTextStyleAction extends Action<ToggleTextStyleIntent> { |
||||||
|
QuillEditorToggleTextStyleAction(this.state); |
||||||
|
|
||||||
|
final QuillRawEditorState state; |
||||||
|
|
||||||
|
bool _isStyleActive(Attribute styleAttr, Map<String, Attribute> attrs) { |
||||||
|
if (styleAttr.key == Attribute.list.key) { |
||||||
|
final attribute = attrs[styleAttr.key]; |
||||||
|
if (attribute == null) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
return attribute.value == styleAttr.value; |
||||||
|
} |
||||||
|
return attrs.containsKey(styleAttr.key); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void invoke(ToggleTextStyleIntent intent, [BuildContext? context]) { |
||||||
|
final isActive = _isStyleActive( |
||||||
|
intent.attribute, state.controller.getSelectionStyle().attributes); |
||||||
|
state.controller.formatSelection( |
||||||
|
isActive ? Attribute.clone(intent.attribute, null) : intent.attribute); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
bool get isActionEnabled => true; |
||||||
|
} |
||||||
|
|
||||||
|
class IndentSelectionIntent extends Intent { |
||||||
|
const IndentSelectionIntent(this.isIncrease); |
||||||
|
|
||||||
|
final bool isIncrease; |
||||||
|
} |
||||||
|
|
||||||
|
// Toggles a text style (underline, bold, italic, strikethrough) on, or off. |
||||||
|
class QuillEditorIndentSelectionAction extends Action<IndentSelectionIntent> { |
||||||
|
QuillEditorIndentSelectionAction(this.state); |
||||||
|
|
||||||
|
final QuillRawEditorState state; |
||||||
|
|
||||||
|
@override |
||||||
|
void invoke(IndentSelectionIntent intent, [BuildContext? context]) { |
||||||
|
state.controller.indentSelection(intent.isIncrease); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
bool get isActionEnabled => true; |
||||||
|
} |
||||||
|
|
||||||
|
class OpenSearchIntent extends Intent { |
||||||
|
const OpenSearchIntent(); |
||||||
|
} |
||||||
|
|
||||||
|
// Toggles a text style (underline, bold, italic, strikethrough) on, or off. |
||||||
|
class QuillEditorOpenSearchAction extends ContextAction<OpenSearchIntent> { |
||||||
|
QuillEditorOpenSearchAction(this.state); |
||||||
|
|
||||||
|
final QuillRawEditorState state; |
||||||
|
|
||||||
|
@override |
||||||
|
Future invoke(OpenSearchIntent intent, [BuildContext? context]) async { |
||||||
|
if (context == null) { |
||||||
|
throw ArgumentError( |
||||||
|
'The context should not be null to use invoke() method', |
||||||
|
); |
||||||
|
} |
||||||
|
await showDialog<String>( |
||||||
|
context: context, |
||||||
|
builder: (_) => QuillToolbarSearchDialog( |
||||||
|
controller: state.controller, |
||||||
|
text: '', |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
bool get isActionEnabled => true; |
||||||
|
} |
||||||
|
|
||||||
|
class QuillEditorApplyHeaderIntent extends Intent { |
||||||
|
const QuillEditorApplyHeaderIntent(this.header); |
||||||
|
|
||||||
|
final Attribute header; |
||||||
|
} |
||||||
|
|
||||||
|
// Toggles a text style (underline, bold, italic, strikethrough) on, or off. |
||||||
|
class QuillEditorApplyHeaderAction |
||||||
|
extends Action<QuillEditorApplyHeaderIntent> { |
||||||
|
QuillEditorApplyHeaderAction(this.state); |
||||||
|
|
||||||
|
final QuillRawEditorState state; |
||||||
|
|
||||||
|
Attribute<dynamic> _getHeaderValue() { |
||||||
|
return state.controller |
||||||
|
.getSelectionStyle() |
||||||
|
.attributes[Attribute.header.key] ?? |
||||||
|
Attribute.header; |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void invoke(QuillEditorApplyHeaderIntent intent, [BuildContext? context]) { |
||||||
|
final attribute = |
||||||
|
_getHeaderValue() == intent.header ? Attribute.header : intent.header; |
||||||
|
state.controller.formatSelection(attribute); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
bool get isActionEnabled => true; |
||||||
|
} |
||||||
|
|
||||||
|
class QuillEditorApplyCheckListIntent extends Intent { |
||||||
|
const QuillEditorApplyCheckListIntent(); |
||||||
|
} |
||||||
|
|
||||||
|
// Toggles a text style (underline, bold, italic, strikethrough) on, or off. |
||||||
|
class QuillEditorApplyCheckListAction |
||||||
|
extends Action<QuillEditorApplyCheckListIntent> { |
||||||
|
QuillEditorApplyCheckListAction(this.state); |
||||||
|
|
||||||
|
final QuillRawEditorState state; |
||||||
|
|
||||||
|
bool _getIsToggled() { |
||||||
|
final attrs = state.controller.getSelectionStyle().attributes; |
||||||
|
var attribute = state.controller.toolbarButtonToggler[Attribute.list.key]; |
||||||
|
|
||||||
|
if (attribute == null) { |
||||||
|
attribute = attrs[Attribute.list.key]; |
||||||
|
} else { |
||||||
|
// checkbox tapping causes controller.selection to go to offset 0 |
||||||
|
state.controller.toolbarButtonToggler.remove(Attribute.list.key); |
||||||
|
} |
||||||
|
|
||||||
|
if (attribute == null) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
return attribute.value == Attribute.unchecked.value || |
||||||
|
attribute.value == Attribute.checked.value; |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void invoke(QuillEditorApplyCheckListIntent intent, [BuildContext? context]) { |
||||||
|
state.controller.formatSelection(_getIsToggled() |
||||||
|
? Attribute.clone(Attribute.unchecked, null) |
||||||
|
: Attribute.unchecked); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
bool get isActionEnabled => true; |
||||||
|
} |
||||||
|
|
||||||
|
class QuillEditorApplyLinkIntent extends Intent { |
||||||
|
const QuillEditorApplyLinkIntent(); |
||||||
|
} |
||||||
|
|
||||||
|
class QuillEditorApplyLinkAction extends Action<QuillEditorApplyLinkIntent> { |
||||||
|
QuillEditorApplyLinkAction(this.state); |
||||||
|
|
||||||
|
final QuillRawEditorState state; |
||||||
|
|
||||||
|
@override |
||||||
|
Object? invoke(QuillEditorApplyLinkIntent intent) async { |
||||||
|
final initialTextLink = QuillTextLink.prepare(state.controller); |
||||||
|
|
||||||
|
final textLink = await showDialog<QuillTextLink>( |
||||||
|
context: state.context, |
||||||
|
builder: (context) { |
||||||
|
return LinkStyleDialog( |
||||||
|
text: initialTextLink.text, |
||||||
|
link: initialTextLink.link, |
||||||
|
dialogTheme: state.widget.dialogTheme, |
||||||
|
); |
||||||
|
}, |
||||||
|
); |
||||||
|
|
||||||
|
if (textLink != null) { |
||||||
|
textLink.submit(state.controller); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class QuillEditorInsertEmbedIntent extends Intent { |
||||||
|
const QuillEditorInsertEmbedIntent(this.type); |
||||||
|
|
||||||
|
final Attribute type; |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,292 @@ |
|||||||
|
import 'dart:math' as math; |
||||||
|
|
||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:flutter/services.dart' show TextLayoutMetrics; |
||||||
|
|
||||||
|
/// An interface for retrieving the logical text boundary |
||||||
|
/// (left-closed-right-open) |
||||||
|
/// at a given location in a document. |
||||||
|
/// |
||||||
|
/// Depending on the implementation of the [QuillEditorTextBoundary], the input |
||||||
|
/// [TextPosition] can either point to a code unit, or a position between 2 code |
||||||
|
/// units (which can be visually represented by the caret if the selection were |
||||||
|
/// to collapse to that position). |
||||||
|
/// |
||||||
|
/// For example, [QuillEditorLineBreak] interprets the input [TextPosition] as a caret |
||||||
|
/// location, since in Flutter the caret is generally painted between the |
||||||
|
/// character the [TextPosition] points to and its previous character, and |
||||||
|
/// [QuillEditorLineBreak] cares about the affinity of the input [TextPosition]. Most |
||||||
|
/// other text boundaries however, interpret the input [TextPosition] as the |
||||||
|
/// location of a code unit in the document, since it's easier to reason about |
||||||
|
/// the text boundary given a code unit in the text. |
||||||
|
/// |
||||||
|
/// To convert a "code-unit-based" [QuillEditorTextBoundary] to "caret-location-based", |
||||||
|
/// use the [QuillEditorCollapsedSelectionBoundary] combinator. |
||||||
|
abstract class QuillEditorTextBoundary { |
||||||
|
const QuillEditorTextBoundary(); |
||||||
|
|
||||||
|
TextEditingValue get textEditingValue; |
||||||
|
|
||||||
|
/// Returns the leading text boundary at the given location, inclusive. |
||||||
|
TextPosition getLeadingTextBoundaryAt(TextPosition position); |
||||||
|
|
||||||
|
/// Returns the trailing text boundary at the given location, exclusive. |
||||||
|
TextPosition getTrailingTextBoundaryAt(TextPosition position); |
||||||
|
|
||||||
|
TextRange getTextBoundaryAt(TextPosition position) { |
||||||
|
return TextRange( |
||||||
|
start: getLeadingTextBoundaryAt(position).offset, |
||||||
|
end: getTrailingTextBoundaryAt(position).offset, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ----------------------------- Text Boundaries ----------------------------- |
||||||
|
|
||||||
|
// The word modifier generally removes the word boundaries around white spaces |
||||||
|
// (and newlines), IOW white spaces and some other punctuations are considered |
||||||
|
// a part of the next word in the search direction. |
||||||
|
class QuillEditorWhitespaceBoundary extends QuillEditorTextBoundary { |
||||||
|
const QuillEditorWhitespaceBoundary(this.textEditingValue); |
||||||
|
|
||||||
|
@override |
||||||
|
final TextEditingValue textEditingValue; |
||||||
|
|
||||||
|
@override |
||||||
|
TextPosition getLeadingTextBoundaryAt(TextPosition position) { |
||||||
|
for (var index = position.offset; index >= 0; index -= 1) { |
||||||
|
if (!TextLayoutMetrics.isWhitespace( |
||||||
|
textEditingValue.text.codeUnitAt(index))) { |
||||||
|
return TextPosition(offset: index); |
||||||
|
} |
||||||
|
} |
||||||
|
return const TextPosition(offset: 0); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
TextPosition getTrailingTextBoundaryAt(TextPosition position) { |
||||||
|
for (var index = position.offset; |
||||||
|
index < textEditingValue.text.length; |
||||||
|
index += 1) { |
||||||
|
if (!TextLayoutMetrics.isWhitespace( |
||||||
|
textEditingValue.text.codeUnitAt(index))) { |
||||||
|
return TextPosition(offset: index + 1); |
||||||
|
} |
||||||
|
} |
||||||
|
return TextPosition(offset: textEditingValue.text.length); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Most apps delete the entire grapheme when the backspace key is pressed. |
||||||
|
// Also always put the new caret location to character boundaries to avoid |
||||||
|
// sending malformed UTF-16 code units to the paragraph builder. |
||||||
|
class QuillEditorCharacterBoundary extends QuillEditorTextBoundary { |
||||||
|
const QuillEditorCharacterBoundary(this.textEditingValue); |
||||||
|
|
||||||
|
@override |
||||||
|
final TextEditingValue textEditingValue; |
||||||
|
|
||||||
|
@override |
||||||
|
TextPosition getLeadingTextBoundaryAt(TextPosition position) { |
||||||
|
final int endOffset = |
||||||
|
math.min(position.offset + 1, textEditingValue.text.length); |
||||||
|
return TextPosition( |
||||||
|
offset: |
||||||
|
CharacterRange.at(textEditingValue.text, position.offset, endOffset) |
||||||
|
.stringBeforeLength, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
TextPosition getTrailingTextBoundaryAt(TextPosition position) { |
||||||
|
final int endOffset = |
||||||
|
math.min(position.offset + 1, textEditingValue.text.length); |
||||||
|
final range = |
||||||
|
CharacterRange.at(textEditingValue.text, position.offset, endOffset); |
||||||
|
return TextPosition( |
||||||
|
offset: textEditingValue.text.length - range.stringAfterLength, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
TextRange getTextBoundaryAt(TextPosition position) { |
||||||
|
final int endOffset = |
||||||
|
math.min(position.offset + 1, textEditingValue.text.length); |
||||||
|
final range = |
||||||
|
CharacterRange.at(textEditingValue.text, position.offset, endOffset); |
||||||
|
return TextRange( |
||||||
|
start: range.stringBeforeLength, |
||||||
|
end: textEditingValue.text.length - range.stringAfterLength, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// [UAX #29](https://unicode.org/reports/tr29/) defined word boundaries. |
||||||
|
class QuillEditorWordBoundary extends QuillEditorTextBoundary { |
||||||
|
const QuillEditorWordBoundary(this.textLayout, this.textEditingValue); |
||||||
|
|
||||||
|
final TextLayoutMetrics textLayout; |
||||||
|
|
||||||
|
@override |
||||||
|
final TextEditingValue textEditingValue; |
||||||
|
|
||||||
|
@override |
||||||
|
TextPosition getLeadingTextBoundaryAt(TextPosition position) { |
||||||
|
return TextPosition( |
||||||
|
offset: textLayout.getWordBoundary(position).start, |
||||||
|
// Word boundary seems to always report downstream on many platforms. |
||||||
|
affinity: |
||||||
|
TextAffinity.downstream, // ignore: avoid_redundant_argument_values |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
TextPosition getTrailingTextBoundaryAt(TextPosition position) { |
||||||
|
return TextPosition( |
||||||
|
offset: textLayout.getWordBoundary(position).end, |
||||||
|
// Word boundary seems to always report downstream on many platforms. |
||||||
|
affinity: |
||||||
|
TextAffinity.downstream, // ignore: avoid_redundant_argument_values |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// The linebreaks of the current text layout. The input [TextPosition]s are |
||||||
|
// interpreted as caret locations because [TextPainter.getLineAtOffset] is |
||||||
|
// text-affinity-aware. |
||||||
|
class QuillEditorLineBreak extends QuillEditorTextBoundary { |
||||||
|
const QuillEditorLineBreak(this.textLayout, this.textEditingValue); |
||||||
|
|
||||||
|
final TextLayoutMetrics textLayout; |
||||||
|
|
||||||
|
@override |
||||||
|
final TextEditingValue textEditingValue; |
||||||
|
|
||||||
|
@override |
||||||
|
TextPosition getLeadingTextBoundaryAt(TextPosition position) { |
||||||
|
return TextPosition( |
||||||
|
offset: textLayout.getLineAtOffset(position).start, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
TextPosition getTrailingTextBoundaryAt(TextPosition position) { |
||||||
|
return TextPosition( |
||||||
|
offset: textLayout.getLineAtOffset(position).end, |
||||||
|
affinity: TextAffinity.upstream, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// The document boundary is unique and is a constant function of the input |
||||||
|
// position. |
||||||
|
class QuillEditorDocumentBoundary extends QuillEditorTextBoundary { |
||||||
|
const QuillEditorDocumentBoundary(this.textEditingValue); |
||||||
|
|
||||||
|
@override |
||||||
|
final TextEditingValue textEditingValue; |
||||||
|
|
||||||
|
@override |
||||||
|
TextPosition getLeadingTextBoundaryAt(TextPosition position) => |
||||||
|
const TextPosition(offset: 0); |
||||||
|
|
||||||
|
@override |
||||||
|
TextPosition getTrailingTextBoundaryAt(TextPosition position) { |
||||||
|
return TextPosition( |
||||||
|
offset: textEditingValue.text.length, |
||||||
|
affinity: TextAffinity.upstream, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ------------------------ Text Boundary Combinators ------------------------ |
||||||
|
|
||||||
|
// Expands the innerTextBoundary with outerTextBoundary. |
||||||
|
class QuillEditorExpandedTextBoundary extends QuillEditorTextBoundary { |
||||||
|
QuillEditorExpandedTextBoundary( |
||||||
|
this.innerTextBoundary, this.outerTextBoundary); |
||||||
|
|
||||||
|
final QuillEditorTextBoundary innerTextBoundary; |
||||||
|
final QuillEditorTextBoundary outerTextBoundary; |
||||||
|
|
||||||
|
@override |
||||||
|
TextEditingValue get textEditingValue { |
||||||
|
assert(innerTextBoundary.textEditingValue == |
||||||
|
outerTextBoundary.textEditingValue); |
||||||
|
return innerTextBoundary.textEditingValue; |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
TextPosition getLeadingTextBoundaryAt(TextPosition position) { |
||||||
|
return outerTextBoundary.getLeadingTextBoundaryAt( |
||||||
|
innerTextBoundary.getLeadingTextBoundaryAt(position), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
TextPosition getTrailingTextBoundaryAt(TextPosition position) { |
||||||
|
return outerTextBoundary.getTrailingTextBoundaryAt( |
||||||
|
innerTextBoundary.getTrailingTextBoundaryAt(position), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Force the innerTextBoundary to interpret the input [TextPosition]s as caret |
||||||
|
// locations instead of code unit positions. |
||||||
|
// |
||||||
|
// The innerTextBoundary must be a [_TextBoundary] that interprets the input |
||||||
|
// [TextPosition]s as code unit positions. |
||||||
|
class QuillEditorCollapsedSelectionBoundary extends QuillEditorTextBoundary { |
||||||
|
QuillEditorCollapsedSelectionBoundary(this.innerTextBoundary, this.isForward); |
||||||
|
|
||||||
|
final QuillEditorTextBoundary innerTextBoundary; |
||||||
|
final bool isForward; |
||||||
|
|
||||||
|
@override |
||||||
|
TextEditingValue get textEditingValue => innerTextBoundary.textEditingValue; |
||||||
|
|
||||||
|
@override |
||||||
|
TextPosition getLeadingTextBoundaryAt(TextPosition position) { |
||||||
|
return isForward |
||||||
|
? innerTextBoundary.getLeadingTextBoundaryAt(position) |
||||||
|
: position.offset <= 0 |
||||||
|
? const TextPosition(offset: 0) |
||||||
|
: innerTextBoundary.getLeadingTextBoundaryAt( |
||||||
|
TextPosition(offset: position.offset - 1)); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
TextPosition getTrailingTextBoundaryAt(TextPosition position) { |
||||||
|
return isForward |
||||||
|
? innerTextBoundary.getTrailingTextBoundaryAt(position) |
||||||
|
: position.offset <= 0 |
||||||
|
? const TextPosition(offset: 0) |
||||||
|
: innerTextBoundary.getTrailingTextBoundaryAt( |
||||||
|
TextPosition(offset: position.offset - 1)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// A _TextBoundary that creates a [TextRange] where its start is from the |
||||||
|
// specified leading text boundary and its end is from the specified trailing |
||||||
|
// text boundary. |
||||||
|
class QuillEditorMixedBoundary extends QuillEditorTextBoundary { |
||||||
|
QuillEditorMixedBoundary(this.leadingTextBoundary, this.trailingTextBoundary); |
||||||
|
|
||||||
|
final QuillEditorTextBoundary leadingTextBoundary; |
||||||
|
final QuillEditorTextBoundary trailingTextBoundary; |
||||||
|
|
||||||
|
@override |
||||||
|
TextEditingValue get textEditingValue { |
||||||
|
assert(leadingTextBoundary.textEditingValue == |
||||||
|
trailingTextBoundary.textEditingValue); |
||||||
|
return leadingTextBoundary.textEditingValue; |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
TextPosition getLeadingTextBoundaryAt(TextPosition position) => |
||||||
|
leadingTextBoundary.getLeadingTextBoundaryAt(position); |
||||||
|
|
||||||
|
@override |
||||||
|
TextPosition getTrailingTextBoundaryAt(TextPosition position) => |
||||||
|
trailingTextBoundary.getTrailingTextBoundaryAt(position); |
||||||
|
} |
@ -0,0 +1,86 @@ |
|||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:flutter/rendering.dart' show ViewportOffset; |
||||||
|
|
||||||
|
import '../../models/documents/document.dart'; |
||||||
|
import '../cursor.dart'; |
||||||
|
import '../editor/editor.dart'; |
||||||
|
|
||||||
|
class QuilRawEditorMultiChildRenderObjectWidget |
||||||
|
extends MultiChildRenderObjectWidget { |
||||||
|
const QuilRawEditorMultiChildRenderObjectWidget({ |
||||||
|
required super.children, |
||||||
|
required this.document, |
||||||
|
required this.textDirection, |
||||||
|
required this.hasFocus, |
||||||
|
required this.scrollable, |
||||||
|
required this.selection, |
||||||
|
required this.startHandleLayerLink, |
||||||
|
required this.endHandleLayerLink, |
||||||
|
required this.onSelectionChanged, |
||||||
|
required this.onSelectionCompleted, |
||||||
|
required this.scrollBottomInset, |
||||||
|
required this.cursorController, |
||||||
|
required this.floatingCursorDisabled, |
||||||
|
super.key, |
||||||
|
this.padding = EdgeInsets.zero, |
||||||
|
this.maxContentWidth, |
||||||
|
this.offset, |
||||||
|
}); |
||||||
|
|
||||||
|
final ViewportOffset? offset; |
||||||
|
final Document document; |
||||||
|
final TextDirection textDirection; |
||||||
|
final bool hasFocus; |
||||||
|
final bool scrollable; |
||||||
|
final TextSelection selection; |
||||||
|
final LayerLink startHandleLayerLink; |
||||||
|
final LayerLink endHandleLayerLink; |
||||||
|
final TextSelectionChangedHandler onSelectionChanged; |
||||||
|
final TextSelectionCompletedHandler onSelectionCompleted; |
||||||
|
final double scrollBottomInset; |
||||||
|
final EdgeInsetsGeometry padding; |
||||||
|
final double? maxContentWidth; |
||||||
|
final CursorCont cursorController; |
||||||
|
final bool floatingCursorDisabled; |
||||||
|
|
||||||
|
@override |
||||||
|
RenderEditor createRenderObject(BuildContext context) { |
||||||
|
return RenderEditor( |
||||||
|
offset: offset, |
||||||
|
document: document, |
||||||
|
textDirection: textDirection, |
||||||
|
hasFocus: hasFocus, |
||||||
|
scrollable: scrollable, |
||||||
|
selection: selection, |
||||||
|
startHandleLayerLink: startHandleLayerLink, |
||||||
|
endHandleLayerLink: endHandleLayerLink, |
||||||
|
onSelectionChanged: onSelectionChanged, |
||||||
|
onSelectionCompleted: onSelectionCompleted, |
||||||
|
cursorController: cursorController, |
||||||
|
padding: padding, |
||||||
|
maxContentWidth: maxContentWidth, |
||||||
|
scrollBottomInset: scrollBottomInset, |
||||||
|
floatingCursorDisabled: floatingCursorDisabled, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void updateRenderObject( |
||||||
|
BuildContext context, |
||||||
|
covariant RenderEditor renderObject, |
||||||
|
) { |
||||||
|
renderObject |
||||||
|
..offset = offset |
||||||
|
..document = document |
||||||
|
..setContainer(document.root) |
||||||
|
..textDirection = textDirection |
||||||
|
..setHasFocus(hasFocus) |
||||||
|
..setSelection(selection) |
||||||
|
..setStartHandleLayerLink(startHandleLayerLink) |
||||||
|
..setEndHandleLayerLink(endHandleLayerLink) |
||||||
|
..onSelectionChanged = onSelectionChanged |
||||||
|
..setScrollBottomInset(scrollBottomInset) |
||||||
|
..setPadding(padding) |
||||||
|
..maxContentWidth = maxContentWidth; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,3 @@ |
|||||||
|
dependency_overrides: |
||||||
|
flutter_quill_test: |
||||||
|
path: ./flutter_quill_test |
@ -0,0 +1,20 @@ |
|||||||
|
#!/bin/bash |
||||||
|
|
||||||
|
echo "" |
||||||
|
|
||||||
|
echo "Disable local development for flutter_quill:" |
||||||
|
rm pubspec_overrides.yaml |
||||||
|
|
||||||
|
echo "" |
||||||
|
|
||||||
|
echo "Enable local development for flutter_quill_extensions:" |
||||||
|
rm flutter_quill_extensions/pubspec_overrides.yaml |
||||||
|
|
||||||
|
echo "" |
||||||
|
|
||||||
|
echo "Enable local development for flutter_quill_test:" |
||||||
|
rm flutter_quill_test/pubspec_overrides.yaml |
||||||
|
|
||||||
|
echo "" |
||||||
|
|
||||||
|
echo "Local development for all libraries has been disabled, please 'flutter pub get' for each one of them" |
@ -0,0 +1,20 @@ |
|||||||
|
#!/bin/bash |
||||||
|
|
||||||
|
echo "" |
||||||
|
|
||||||
|
echo "Enable local development for flutter_quill:" |
||||||
|
cp pubspec_overrides.yaml.g pubspec_overrides.yaml |
||||||
|
|
||||||
|
echo "" |
||||||
|
|
||||||
|
echo "Enable local development for flutter_quill_extensions:" |
||||||
|
cp flutter_quill_extensions/pubspec_overrides.yaml.g flutter_quill_extensions/pubspec_overrides.yaml |
||||||
|
|
||||||
|
echo "" |
||||||
|
|
||||||
|
echo "Enable local development for flutter_quill_test:" |
||||||
|
cp flutter_quill_test/pubspec_overrides.yaml.g flutter_quill_test/pubspec_overrides.yaml |
||||||
|
|
||||||
|
echo "" |
||||||
|
|
||||||
|
echo "Local development for all libraries has been enabled, please 'flutter pub get' for each one of them" |
Loading…
Reference in new issue