CR Small Changes
* regex fix http?s -> https? * file rename: formatter.dart -> markdown_formatter.dart to match the class name inside * add + as continuoable list * rename: listContinuation -> unorderedListContinuation numberedListContinuation -> orderedListContinuation * fix typo: convenience * fix: doc instead of comment * rename for readability: startingIndex -> from * function & arg rename: lineBefore(int endingIndex) -> lineUpTo(int characterIndex) * parse -> tryParse * localize user picker & commmunity picker * HookWidget -> StatelessWidget where needed * Toolbar -> EditorToolbar for less ambiguity * fix typo: surroungs -> surrounds * remove debug logA * more localization stuff * title -> label on add link dialog * Reformat -> _Reformat * use store when in scope instead of context.read * remove useless Stack (oops)
This commit is contained in:
parent
821558314e
commit
cda72a1174
|
@ -415,5 +415,29 @@
|
|||
"insert_text_here_placeholder": "[write text here]",
|
||||
"@insert_text_here_placeholder": {
|
||||
"description": "placeholder for text in markdown editor when inserting stuff like * for italics or ** for bold, etc."
|
||||
},
|
||||
"select_user": "Select User",
|
||||
"@select_user": {
|
||||
"description": "Title on a popup that lets a user search and select another user"
|
||||
},
|
||||
"select_community": "Select Community",
|
||||
"@select_community": {
|
||||
"description": "Title on a popup that lets a user search and select a community"
|
||||
},
|
||||
"add_link": "Add link",
|
||||
"@add_link": {
|
||||
"description": "title on top of a link insertion popup in a markdown editor"
|
||||
},
|
||||
"cancel": "Cancel",
|
||||
"@cancel": {
|
||||
"description": "Cancel button on popup"
|
||||
},
|
||||
"editor_add_link_label": "label",
|
||||
"@editor_add_link_label": {
|
||||
"description": "palceholder for link label on an Add link popup in markdown editor"
|
||||
},
|
||||
"failed_to_upload_image": "Failed to upload image",
|
||||
"@failed_to_upload_image": {
|
||||
"description": "shows up on a snackbar when the image upload failed (duh)"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import 'package:flutter/services.dart';
|
||||
|
||||
extension Utilities on String {
|
||||
int getBeginningOfTheLine(int startingIndex) {
|
||||
if (startingIndex <= 0) return 0;
|
||||
for (var i = startingIndex; i >= 0; i--) {
|
||||
int getBeginningOfTheLine(int from) {
|
||||
if (from <= 0) return 0;
|
||||
for (var i = from; i >= 0; i--) {
|
||||
if (this[i] == '\n') return i + 1;
|
||||
}
|
||||
return 0;
|
||||
|
@ -17,9 +17,9 @@ extension Utilities on String {
|
|||
return length - 1;
|
||||
}
|
||||
|
||||
// returns the line that ends at endingIndex
|
||||
String lineBefore(int endingIndex) {
|
||||
return substring(getBeginningOfTheLine(endingIndex), endingIndex + 1);
|
||||
/// returns the line that ends at endingIndex
|
||||
String lineUpTo(int characterIndex) {
|
||||
return substring(getBeginningOfTheLine(characterIndex), characterIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,7 @@ extension on TextEditingValue {
|
|||
}
|
||||
}
|
||||
|
||||
/// Provides convinience formatting in markdown text fields
|
||||
/// Provides convenience formatting in markdown text fields
|
||||
class MarkdownFormatter extends TextInputFormatter {
|
||||
@override
|
||||
TextEditingValue formatEditUpdate(
|
||||
|
@ -52,9 +52,10 @@ class MarkdownFormatter extends TextInputFormatter {
|
|||
|
||||
if (char == '\n') {
|
||||
final lineBefore =
|
||||
newValue.text.lineBefore(newValue.selection.baseOffset - 2);
|
||||
newValue.text.lineUpTo(newValue.selection.baseOffset - 2);
|
||||
|
||||
TextEditingValue listContinuation(String listChar, TextEditingValue tev) {
|
||||
TextEditingValue unorderedListContinuation(
|
||||
String listChar, TextEditingValue tev) {
|
||||
final regex = RegExp(r'(\s*)' '${RegExp.escape(listChar)} ');
|
||||
final match = regex.matchAsPrefix(lineBefore);
|
||||
if (match == null) {
|
||||
|
@ -65,7 +66,7 @@ class MarkdownFormatter extends TextInputFormatter {
|
|||
return tev.append('$indent$listChar ');
|
||||
}
|
||||
|
||||
TextEditingValue numberedListContinuation(
|
||||
TextEditingValue orderedListContinuation(
|
||||
String afterNumberChar, TextEditingValue tev) {
|
||||
final regex =
|
||||
RegExp(r'(\s*)(\d+)' '${RegExp.escape(afterNumberChar)} ');
|
||||
|
@ -74,15 +75,16 @@ class MarkdownFormatter extends TextInputFormatter {
|
|||
return tev;
|
||||
}
|
||||
final indent = match.group(1);
|
||||
final number = int.parse(match.group(2)!) + 1;
|
||||
final number = int.tryParse(match.group(2)!) ?? 0 + 1;
|
||||
|
||||
return tev.append('$indent$number$afterNumberChar ');
|
||||
}
|
||||
|
||||
newVal = listContinuation('-', newVal);
|
||||
newVal = listContinuation('*', newVal);
|
||||
newVal = numberedListContinuation('.', newVal);
|
||||
newVal = numberedListContinuation(')', newVal);
|
||||
newVal = unorderedListContinuation('-', newVal);
|
||||
newVal = unorderedListContinuation('*', newVal);
|
||||
newVal = unorderedListContinuation('+', newVal);
|
||||
newVal = orderedListContinuation('.', newVal);
|
||||
newVal = orderedListContinuation(')', newVal);
|
||||
}
|
||||
|
||||
return newVal;
|
|
@ -134,7 +134,7 @@ class CreatePostPage extends HookWidget {
|
|||
)
|
||||
],
|
||||
),
|
||||
Toolbar.safeArea,
|
||||
EditorToolbar.safeArea,
|
||||
].spaced(6),
|
||||
),
|
||||
),
|
||||
|
@ -145,7 +145,7 @@ class CreatePostPage extends HookWidget {
|
|||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Spacer(),
|
||||
Toolbar(
|
||||
EditorToolbar(
|
||||
controller: bodyController,
|
||||
instanceHost: context.read<CreatePostStore>().instanceHost,
|
||||
),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
|
||||
import '../../formatter.dart';
|
||||
import '../../markdown_formatter.dart';
|
||||
import '../markdown_text.dart';
|
||||
|
||||
export 'editor_toolbar.dart';
|
||||
|
@ -52,22 +52,18 @@ class Editor extends HookWidget {
|
|||
);
|
||||
}
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
TextField(
|
||||
controller: actualController,
|
||||
focusNode: focusNode,
|
||||
autofocus: autofocus,
|
||||
keyboardType: TextInputType.multiline,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
onChanged: onChanged,
|
||||
onSubmitted: onSubmitted,
|
||||
maxLines: maxLines,
|
||||
minLines: minLines,
|
||||
decoration: InputDecoration(labelText: labelText),
|
||||
inputFormatters: [MarkdownFormatter()],
|
||||
),
|
||||
],
|
||||
return TextField(
|
||||
controller: actualController,
|
||||
focusNode: focusNode,
|
||||
autofocus: autofocus,
|
||||
keyboardType: TextInputType.multiline,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
onChanged: onChanged,
|
||||
onSubmitted: onSubmitted,
|
||||
maxLines: maxLines,
|
||||
minLines: minLines,
|
||||
decoration: InputDecoration(labelText: labelText),
|
||||
inputFormatters: [MarkdownFormatter()],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
||||
import 'package:lemmy_api_client/v3.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../../util/extensions/api.dart';
|
||||
import '../../../widgets/avatar.dart';
|
||||
import '../../l10n/l10n.dart';
|
||||
import '../../stores/accounts_store.dart';
|
||||
import 'editor_toolbar_store.dart';
|
||||
|
||||
class PickPersonDialog extends HookWidget {
|
||||
class PickPersonDialog extends StatelessWidget {
|
||||
final EditorToolbarStore store;
|
||||
|
||||
const PickPersonDialog._(this.store);
|
||||
|
@ -20,7 +20,7 @@ class PickPersonDialog extends HookWidget {
|
|||
context.read<AccountsStore>().defaultUserDataFor(store.instanceHost);
|
||||
|
||||
return AlertDialog(
|
||||
title: const Text('Select User'),
|
||||
title: Text(L10n.of(context).select_user),
|
||||
content: TypeAheadField<PersonViewSafe>(
|
||||
suggestionsCallback: (pattern) async {
|
||||
if (pattern.trim().isEmpty) return const Iterable.empty();
|
||||
|
@ -70,7 +70,7 @@ class PickPersonDialog extends HookWidget {
|
|||
}
|
||||
}
|
||||
|
||||
class PickCommunityDialog extends HookWidget {
|
||||
class PickCommunityDialog extends StatelessWidget {
|
||||
final EditorToolbarStore store;
|
||||
|
||||
const PickCommunityDialog._(this.store);
|
||||
|
@ -81,7 +81,7 @@ class PickCommunityDialog extends HookWidget {
|
|||
context.read<AccountsStore>().defaultUserDataFor(store.instanceHost);
|
||||
|
||||
return AlertDialog(
|
||||
title: const Text('Select Community'),
|
||||
title: Text(L10n.of(context).select_community),
|
||||
content: TypeAheadField<CommunityView>(
|
||||
suggestionsCallback: (pattern) async {
|
||||
if (pattern.trim().isEmpty) return const Iterable.empty();
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
import '../../formatter.dart';
|
||||
import '../../hooks/logged_in_action.dart';
|
||||
import '../../l10n/l10n.dart';
|
||||
import '../../markdown_formatter.dart';
|
||||
import '../../resources/links.dart';
|
||||
import '../../url_launcher.dart';
|
||||
import '../../util/async_store_listener.dart';
|
||||
|
@ -18,127 +16,17 @@ import '../../util/text_lines_iterator.dart';
|
|||
import 'editor_picking_dialog.dart';
|
||||
import 'editor_toolbar_store.dart';
|
||||
|
||||
class Reformat {
|
||||
class _Reformat {
|
||||
final String text;
|
||||
final int selectionBeginningShift;
|
||||
final int selectionEndingShift;
|
||||
Reformat({
|
||||
_Reformat({
|
||||
required this.text,
|
||||
this.selectionBeginningShift = 0,
|
||||
this.selectionEndingShift = 0,
|
||||
});
|
||||
}
|
||||
|
||||
extension on TextEditingController {
|
||||
String get selectionText =>
|
||||
text.substring(selection.baseOffset, selection.extentOffset);
|
||||
String get beforeSelectionText => text.substring(0, selection.baseOffset);
|
||||
String get afterSelectionText => text.substring(selection.extentOffset);
|
||||
|
||||
/// surroungs selection with given strings. If nothing is selected, placeholder is used in the middle
|
||||
void surround({
|
||||
required String before,
|
||||
required String placeholder,
|
||||
|
||||
/// after = before if null
|
||||
String? after,
|
||||
}) {
|
||||
after ??= before;
|
||||
final beg = text.substring(0, selection.baseOffset);
|
||||
final mid = () {
|
||||
final m = text.substring(selection.baseOffset, selection.extentOffset);
|
||||
if (m.isEmpty) return placeholder;
|
||||
return m;
|
||||
}();
|
||||
final end = text.substring(selection.extentOffset);
|
||||
|
||||
value = value.copyWith(
|
||||
text: '$beg$before$mid$after$end',
|
||||
selection: selection.copyWith(
|
||||
baseOffset: selection.baseOffset + before.length,
|
||||
extentOffset: selection.baseOffset + before.length + mid.length,
|
||||
));
|
||||
}
|
||||
|
||||
String get firstSelectedLine {
|
||||
if (text.isEmpty) return '';
|
||||
return text.substring(text.getBeginningOfTheLine(selection.start - 1),
|
||||
text.getEndOfTheLine(selection.end));
|
||||
}
|
||||
|
||||
void insertAtBeginningOfFirstSelectedLine(String s) {
|
||||
final lines = TextLinesIterator.fromController(this)..moveNext();
|
||||
lines.current = s + lines.current;
|
||||
value = value.copyWith(
|
||||
text: lines.text,
|
||||
selection: selection.copyWith(
|
||||
baseOffset: selection.baseOffset + s.length,
|
||||
extentOffset: selection.extentOffset + s.length,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void removeAtBeginningOfEverySelectedLine(String s) {
|
||||
final lines = TextLinesIterator.fromController(this);
|
||||
var linesCount = 0;
|
||||
while (lines.moveNext()) {
|
||||
if (lines.isWithinSelection) {
|
||||
if (lines.current.startsWith(RegExp.escape(s))) {
|
||||
lines.current = lines.current.substring(s.length);
|
||||
linesCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
value = value.copyWith(
|
||||
text: lines.text,
|
||||
selection: selection.copyWith(
|
||||
baseOffset: selection.baseOffset - s.length,
|
||||
extentOffset: selection.extentOffset - s.length * linesCount,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void insertAtBeginningOfEverySelectedLine(String s) {
|
||||
final lines = TextLinesIterator.fromController(this);
|
||||
var linesCount = 0;
|
||||
while (lines.moveNext()) {
|
||||
if (lines.isWithinSelection) {
|
||||
if (!lines.current.startsWith(RegExp.escape(s))) {
|
||||
lines.current = '$s${lines.current}';
|
||||
linesCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
value = value.copyWith(
|
||||
text: lines.text,
|
||||
selection: selection.copyWith(
|
||||
baseOffset: selection.baseOffset + s.length,
|
||||
extentOffset: selection.extentOffset + s.length * linesCount,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void reformat(Reformat Function(String selection) reformatter) {
|
||||
final beg = beforeSelectionText;
|
||||
final mid = selectionText;
|
||||
final end = afterSelectionText;
|
||||
|
||||
final r = reformatter(mid);
|
||||
value = value.copyWith(
|
||||
text: '$beg${r.text}$end',
|
||||
selection: selection.copyWith(
|
||||
baseOffset: selection.baseOffset + r.selectionBeginningShift,
|
||||
extentOffset: selection.extentOffset + r.selectionEndingShift,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void reformatSimple(String text) =>
|
||||
reformat((selection) => Reformat(text: text));
|
||||
}
|
||||
|
||||
enum HeaderLevel {
|
||||
h1(1),
|
||||
h2(2),
|
||||
|
@ -151,21 +39,21 @@ enum HeaderLevel {
|
|||
final int value;
|
||||
}
|
||||
|
||||
class Toolbar extends HookWidget {
|
||||
class EditorToolbar extends StatelessWidget {
|
||||
final TextEditingController controller;
|
||||
final String instanceHost;
|
||||
final EditorToolbarStore store;
|
||||
static const _height = 50.0;
|
||||
|
||||
Toolbar({
|
||||
EditorToolbar({
|
||||
required this.controller,
|
||||
required this.instanceHost,
|
||||
}) : store = EditorToolbarStore(instanceHost);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MobxProvider.value(
|
||||
value: store,
|
||||
return MobxProvider(
|
||||
create: (context) => store,
|
||||
child: AsyncStoreListener(
|
||||
asyncStore: store.imageUploadState,
|
||||
child: Container(
|
||||
|
@ -235,22 +123,19 @@ class _ToolbarBody extends HookWidget {
|
|||
return;
|
||||
}
|
||||
try {
|
||||
// FIXME: for some reason it doesn't go past this line on iOS. idk why
|
||||
final pic = await pickImage();
|
||||
// pic is null when the picker was cancelled
|
||||
|
||||
if (pic != null) {
|
||||
final picUrl = await context
|
||||
.read<EditorToolbarStore>()
|
||||
.uploadImage(pic.path, token);
|
||||
final picUrl = await store.uploadImage(pic.path, token);
|
||||
|
||||
if (picUrl != null) {
|
||||
controller.reformatSimple('![]($picUrl)');
|
||||
}
|
||||
}
|
||||
} on Exception catch (_) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Failed to upload image')));
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text(L10n.of(context).failed_to_upload_image)));
|
||||
}
|
||||
}),
|
||||
icon: store.imageUploadState.isLoading
|
||||
|
@ -366,9 +251,7 @@ class _ToolbarBody extends HookWidget {
|
|||
onPressed: () {
|
||||
controller.reformat((selection) {
|
||||
final insides = selection.isNotEmpty ? selection : '___';
|
||||
Logger.root
|
||||
.info([21, 21 + insides.length, insides, insides.length]);
|
||||
return Reformat(
|
||||
return _Reformat(
|
||||
text: '\n::: spoiler spoiler\n$insides\n:::\n',
|
||||
selectionBeginningShift: 21,
|
||||
selectionEndingShift: 21 + insides.length - selection.length,
|
||||
|
@ -391,31 +274,31 @@ class _ToolbarBody extends HookWidget {
|
|||
}
|
||||
|
||||
class AddLinkDialog extends HookWidget {
|
||||
final String title;
|
||||
final String label;
|
||||
final String url;
|
||||
final String selection;
|
||||
|
||||
static final _websiteRegex = RegExp(r'https?:\/\/', caseSensitive: false);
|
||||
|
||||
AddLinkDialog(this.selection)
|
||||
: title = selection.startsWith(_websiteRegex) ? '' : selection,
|
||||
: label = selection.startsWith(_websiteRegex) ? '' : selection,
|
||||
url = selection.startsWith(_websiteRegex) ? selection : '';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final titleController = useTextEditingController(text: title);
|
||||
final labelController = useTextEditingController(text: label);
|
||||
final urlController = useTextEditingController(text: url);
|
||||
|
||||
void submit() {
|
||||
final link = () {
|
||||
if (urlController.text.startsWith('http?s://')) {
|
||||
if (urlController.text.startsWith(RegExp('https?://'))) {
|
||||
return urlController.text;
|
||||
} else {
|
||||
return 'https://${urlController.text}';
|
||||
}
|
||||
}();
|
||||
final finalString = '(${titleController.text})[$link]';
|
||||
Navigator.of(context).pop(Reformat(
|
||||
final finalString = '[${labelController.text}]($link)';
|
||||
Navigator.of(context).pop(_Reformat(
|
||||
text: finalString,
|
||||
selectionBeginningShift: finalString.length,
|
||||
selectionEndingShift: finalString.length - selection.length,
|
||||
|
@ -423,13 +306,14 @@ class AddLinkDialog extends HookWidget {
|
|||
}
|
||||
|
||||
return AlertDialog(
|
||||
title: const Text('Add link'),
|
||||
title: Text(L10n.of(context).add_link),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
controller: titleController,
|
||||
decoration: const InputDecoration(hintText: 'title'),
|
||||
controller: labelController,
|
||||
decoration: InputDecoration(
|
||||
hintText: L10n.of(context).editor_add_link_label),
|
||||
textInputAction: TextInputAction.next,
|
||||
autofocus: true,
|
||||
),
|
||||
|
@ -444,19 +328,129 @@ class AddLinkDialog extends HookWidget {
|
|||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Cancel')),
|
||||
child: Text(L10n.of(context).cancel)),
|
||||
ElevatedButton(
|
||||
onPressed: submit,
|
||||
child: const Text('Add link'),
|
||||
child: Text(L10n.of(context).add_link),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
static Future<Reformat?> show(BuildContext context, String selection) async {
|
||||
static Future<_Reformat?> show(BuildContext context, String selection) async {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) => AddLinkDialog(selection),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension on TextEditingController {
|
||||
String get selectionText =>
|
||||
text.substring(selection.baseOffset, selection.extentOffset);
|
||||
String get beforeSelectionText => text.substring(0, selection.baseOffset);
|
||||
String get afterSelectionText => text.substring(selection.extentOffset);
|
||||
|
||||
/// surrounds selection with given strings. If nothing is selected, placeholder is used in the middle
|
||||
void surround({
|
||||
required String before,
|
||||
required String placeholder,
|
||||
|
||||
/// after = before if null
|
||||
String? after,
|
||||
}) {
|
||||
after ??= before;
|
||||
final beg = text.substring(0, selection.baseOffset);
|
||||
final mid = () {
|
||||
final m = text.substring(selection.baseOffset, selection.extentOffset);
|
||||
if (m.isEmpty) return placeholder;
|
||||
return m;
|
||||
}();
|
||||
final end = text.substring(selection.extentOffset);
|
||||
|
||||
value = value.copyWith(
|
||||
text: '$beg$before$mid$after$end',
|
||||
selection: selection.copyWith(
|
||||
baseOffset: selection.baseOffset + before.length,
|
||||
extentOffset: selection.baseOffset + before.length + mid.length,
|
||||
));
|
||||
}
|
||||
|
||||
String get firstSelectedLine {
|
||||
if (text.isEmpty) return '';
|
||||
return text.substring(text.getBeginningOfTheLine(selection.start - 1),
|
||||
text.getEndOfTheLine(selection.end));
|
||||
}
|
||||
|
||||
void insertAtBeginningOfFirstSelectedLine(String s) {
|
||||
final lines = TextLinesIterator.fromController(this)..moveNext();
|
||||
lines.current = s + lines.current;
|
||||
value = value.copyWith(
|
||||
text: lines.text,
|
||||
selection: selection.copyWith(
|
||||
baseOffset: selection.baseOffset + s.length,
|
||||
extentOffset: selection.extentOffset + s.length,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void removeAtBeginningOfEverySelectedLine(String s) {
|
||||
final lines = TextLinesIterator.fromController(this);
|
||||
var linesCount = 0;
|
||||
while (lines.moveNext()) {
|
||||
if (lines.isWithinSelection) {
|
||||
if (lines.current.startsWith(RegExp.escape(s))) {
|
||||
lines.current = lines.current.substring(s.length);
|
||||
linesCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
value = value.copyWith(
|
||||
text: lines.text,
|
||||
selection: selection.copyWith(
|
||||
baseOffset: selection.baseOffset - s.length,
|
||||
extentOffset: selection.extentOffset - s.length * linesCount,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void insertAtBeginningOfEverySelectedLine(String s) {
|
||||
final lines = TextLinesIterator.fromController(this);
|
||||
var linesCount = 0;
|
||||
while (lines.moveNext()) {
|
||||
if (lines.isWithinSelection) {
|
||||
if (!lines.current.startsWith(RegExp.escape(s))) {
|
||||
lines.current = '$s${lines.current}';
|
||||
linesCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
value = value.copyWith(
|
||||
text: lines.text,
|
||||
selection: selection.copyWith(
|
||||
baseOffset: selection.baseOffset + s.length,
|
||||
extentOffset: selection.extentOffset + s.length * linesCount,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void reformat(_Reformat Function(String selection) reformatter) {
|
||||
final beg = beforeSelectionText;
|
||||
final mid = selectionText;
|
||||
final end = afterSelectionText;
|
||||
|
||||
final r = reformatter(mid);
|
||||
value = value.copyWith(
|
||||
text: '$beg${r.text}$end',
|
||||
selection: selection.copyWith(
|
||||
baseOffset: selection.baseOffset + r.selectionBeginningShift,
|
||||
extentOffset: selection.extentOffset + r.selectionEndingShift,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void reformatSimple(String text) =>
|
||||
reformat((selection) => _Reformat(text: text));
|
||||
}
|
||||
|
|
|
@ -132,7 +132,7 @@ class WriteComment extends HookWidget {
|
|||
)
|
||||
],
|
||||
),
|
||||
Toolbar.safeArea,
|
||||
EditorToolbar.safeArea,
|
||||
],
|
||||
),
|
||||
SafeArea(
|
||||
|
@ -140,7 +140,7 @@ class WriteComment extends HookWidget {
|
|||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Spacer(),
|
||||
Toolbar(
|
||||
EditorToolbar(
|
||||
controller: controller,
|
||||
instanceHost: post.instanceHost,
|
||||
),
|
||||
|
|
Loading…
Reference in New Issue