Files
GitJournal/lib/widgets/markdown_toolbar.dart
Vishesh Handa d008f0d982 First version of dart migrate
Most of the files haven't been migrated. Lets work on this bit by bit
2021-04-10 22:18:35 +02:00

292 lines
7.7 KiB
Dart

// @dart=2.9
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
// FIXME:
// - Pin this on top of the keyboard
// - It should only be visible when the keyboard is shown
// - Add an overlay which shows the other header options when longtaping on H1
// - Add a block quote button
// - Add a code fence button
//
class MarkdownToolBar extends StatelessWidget {
final TextEditingController textController;
MarkdownToolBar({
@required this.textController,
});
@override
Widget build(BuildContext context) {
var textTheme = Theme.of(context).textTheme;
var style = textTheme.bodyText2.copyWith(fontWeight: FontWeight.bold);
return Container(
child: Row(
children: [
IconButton(
icon: Text('H1', style: style),
padding: const EdgeInsets.all(0.0),
onPressed: () => _modifyCurrentLine('# '),
),
IconButton(
icon: Text('I', style: style),
padding: const EdgeInsets.all(0.0),
onPressed: () => _modifyCurrentWord('*'),
),
IconButton(
icon: Text('B', style: style),
padding: const EdgeInsets.all(0.0),
onPressed: () => _modifyCurrentWord('**'),
),
IconButton(
icon: const FaIcon(FontAwesomeIcons.listUl),
padding: const EdgeInsets.all(0.0),
onPressed: () => _modifyCurrentLine('- '),
),
IconButton(
icon: const FaIcon(FontAwesomeIcons.listOl),
padding: const EdgeInsets.all(0.0),
onPressed: () => _modifyCurrentLine('1. '),
),
IconButton(
icon: const FaIcon(FontAwesomeIcons.tasks),
padding: const EdgeInsets.all(0.0),
onPressed: () => _modifyCurrentLine('- [ ] '),
),
Container(
height: 20,
child: const VerticalDivider(),
),
IconButton(
icon: const Icon(Icons.navigate_before),
padding: const EdgeInsets.all(0.0),
onPressed: _navigateToPrevWord,
),
IconButton(
icon: const Icon(Icons.navigate_next),
padding: const EdgeInsets.all(0.0),
onPressed: _navigateToNextWord,
),
],
),
);
}
void _modifyCurrentLine(String char) {
textController.value = modifyCurrentLine(textController.value, char);
}
void _modifyCurrentWord(String char) {
textController.value = modifyCurrentWord(textController.value, char);
}
void _navigateToPrevWord() {
var offset = prevWordPos(textController.value);
textController.selection = TextSelection.collapsed(offset: offset);
}
void _navigateToNextWord() {
var offset = nextWordPos(textController.value);
textController.selection = TextSelection.collapsed(offset: offset);
}
}
final _allowedBlockTags = [
'# ',
'## ',
'### ',
'#### ',
'##### ',
'###### ',
'- ',
'* ',
];
final _allowedBlockRegExps = [
RegExp('- \[[xX ]\] '),
RegExp('\d+\. '),
];
TextEditingValue modifyCurrentLine(
TextEditingValue textEditingValue,
String char,
) {
var selection = textEditingValue.selection;
var text = textEditingValue.text;
//print('Base offset: ${selection.baseOffset}');
//print('Extent offset: ${selection.extentOffset}');
var cursorPos = selection.baseOffset;
if (cursorPos == -1) {
cursorPos = 0;
}
//print('CursorPos: $cursorPos');
var lineStartPos = text.lastIndexOf('\n', cursorPos == 0 ? 0 : cursorPos - 1);
if (lineStartPos == -1) {
lineStartPos = 0;
} else {
lineStartPos += 1;
}
var lineEndPos = text.indexOf('\n', cursorPos);
if (lineEndPos == -1) {
lineEndPos = text.length;
}
// Check if line already has a block tag
for (var blockTag in _allowedBlockTags) {
if (text.startsWith(blockTag, lineStartPos)) {
var newVal = _removeFromLine(text, cursorPos, lineStartPos, blockTag);
if (blockTag == char) {
return newVal;
}
return modifyCurrentLine(newVal, char);
}
}
for (var blockTagRegExp in _allowedBlockRegExps) {
var match = blockTagRegExp.matchAsPrefix(text, lineStartPos);
if (match != null) {
var blockTag = match.group(0);
var newVal = _removeFromLine(text, cursorPos, lineStartPos, blockTag);
if (blockTag == char) {
return newVal;
}
return modifyCurrentLine(newVal, char);
}
}
//print('Line Start: $lineStartPos');
//print('Line End: $lineEndPos');
//print('Line: ${text.substring(lineStartPos, lineEndPos)}');
return TextEditingValue(
text: text.replaceRange(lineStartPos, lineStartPos, char),
selection: TextSelection.collapsed(offset: cursorPos + char.length),
);
}
TextEditingValue _removeFromLine(
String text,
int cursorPos,
int lineStartPos,
String char,
) {
//print('Removing `$char`');
var endOffset = cursorPos;
//print("End Offset: $endOffset");
if (endOffset > lineStartPos) {
endOffset -= char.length;
//print("End Offset min char: $endOffset");
}
if (endOffset < lineStartPos) {
endOffset = lineStartPos;
//print("End Offset equal LineStartPos: $endOffset");
}
return TextEditingValue(
text: text.replaceFirst(char, '', lineStartPos),
selection: TextSelection.collapsed(offset: endOffset),
);
}
TextEditingValue modifyCurrentWord(
TextEditingValue textEditingValue,
String char,
) {
var selection = textEditingValue.selection;
var text = textEditingValue.text;
//print('Base offset: ${selection.baseOffset}');
//print('Extent offset: ${selection.extentOffset}');
var cursorPos = selection.baseOffset;
if (cursorPos == -1) {
cursorPos = 0;
}
//print('CursorPos: $cursorPos');
var wordStartPos =
text.lastIndexOf(RegExp('\\s'), cursorPos == 0 ? 0 : cursorPos - 1);
if (wordStartPos == -1) {
wordStartPos = 0;
} else {
wordStartPos += 1;
}
var wordEndPos = text.indexOf(RegExp('\\s'), cursorPos);
if (wordEndPos == -1) {
wordEndPos = text.length;
}
//print('Word Start: $wordStartPos');
//print('Word End: $wordEndPos');
//print('Word: ${text.substring(wordStartPos, wordEndPos)}');
// Check if already present
if (text.startsWith(char, wordStartPos) &&
text.startsWith(char, wordEndPos - char.length)) {
text = text.replaceFirst(char, '', wordStartPos);
wordEndPos -= char.length;
return TextEditingValue(
text: text.replaceFirst(char, '', wordEndPos - char.length),
selection: TextSelection.collapsed(
offset: wordEndPos - char.length,
),
);
}
//print('Adding `$char`');
text = text.replaceRange(wordStartPos, wordStartPos, char);
wordEndPos += char.length;
return TextEditingValue(
text: text.replaceRange(wordEndPos, wordEndPos, char),
selection: TextSelection.collapsed(offset: wordEndPos),
);
}
// FIXME: This will fail in non space delimited languages
final _wordSepRegex = RegExp('((\\s|\\n)|[.!?])');
int nextWordPos(TextEditingValue textEditingValue) {
var cursorPos = textEditingValue.selection.baseOffset;
var text = textEditingValue.text;
if (cursorPos >= text.length) {
return text.length;
}
var nextSpacePos = text.indexOf(_wordSepRegex, cursorPos);
if (nextSpacePos == -1) {
return text.length;
}
if (nextSpacePos == cursorPos) {
nextSpacePos++;
}
return nextSpacePos;
}
int prevWordPos(TextEditingValue textEditingValue) {
var cursorPos = textEditingValue.selection.baseOffset;
var text = textEditingValue.text;
if (cursorPos <= 1) {
return 0;
}
var lastSpacePos = text.lastIndexOf(_wordSepRegex, cursorPos - 1);
if (lastSpacePos == -1) {
return 0;
}
if (lastSpacePos == cursorPos - 1) {
lastSpacePos--;
}
return lastSpacePos + 1;
}