mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
relicense appflowy editor (#1938)
* revert:"fix: remove keyword when click selection menu item" This reverts commit5782dec45c
. * revert(appflowy_editor):revert "feat: double asterisks/underscores to bold text" This reverts commitc0964fad5d
. * revert(appflowy_editor):revert "fix: workaround infinity formatting" This reverts commit6a902a2b21
. The Appflowy folder under the frontend had been removed before reverting. * chore(appflow_editor):update test variable after reverting * chore(appflowy_editor): comment out the test for reverting * chore(appflowy_editor): update variable type after reverting * chore(appflowy_editor): remove unused import after reverting * feat(appflowy_editor): double asterisk to bold text * test(appflowy_editor): test double asterisk to bold text * fix(appflowy_editor): delete slash after a selection menu item is selected * test(appflowy_editor): test selection menu widget after clicking * feat(appflowy_editor): double asterisk to bold text and remove slash after clicking selection menu item (#1935) * feat(appflowy_editor): double asterisk to bold text * test(appflowy_editor): test double asterisk to bold text * fix(appflowy_editor): delete slash after a selection menu item is selected * test(appflowy_editor): test selection menu widget after clicking * feat(appflowy_editor): double underscore to bold text * test(appflowy_editor): test double underscore to bold text * chore(appflowy_editor): put checkbox testing back * chore: format code --------- Co-authored-by: Yijing Huang <hyj891204@gmail.com>
This commit is contained in:
@ -7,7 +7,7 @@ import 'package:flowy_infra/image.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
SelectionMenuItem boardMenuItem = SelectionMenuItem(
|
SelectionMenuItem boardMenuItem = SelectionMenuItem(
|
||||||
name: () => LocaleKeys.document_plugins_referencedBoard.tr(),
|
name: LocaleKeys.document_plugins_referencedBoard.tr(),
|
||||||
icon: (editorState, onSelected) {
|
icon: (editorState, onSelected) {
|
||||||
return svgWidget(
|
return svgWidget(
|
||||||
'editor/board',
|
'editor/board',
|
||||||
|
@ -7,7 +7,7 @@ import 'package:flowy_infra/image.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
SelectionMenuItem gridMenuItem = SelectionMenuItem(
|
SelectionMenuItem gridMenuItem = SelectionMenuItem(
|
||||||
name: () => LocaleKeys.document_plugins_referencedGrid.tr(),
|
name: LocaleKeys.document_plugins_referencedGrid.tr(),
|
||||||
icon: (editorState, onSelected) {
|
icon: (editorState, onSelected) {
|
||||||
return svgWidget(
|
return svgWidget(
|
||||||
'editor/grid',
|
'editor/grid',
|
||||||
|
@ -37,7 +37,7 @@ ShortcutEventHandler _insertHorzaontalRule = (editorState, event) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
SelectionMenuItem horizontalRuleMenuItem = SelectionMenuItem(
|
SelectionMenuItem horizontalRuleMenuItem = SelectionMenuItem(
|
||||||
name: () => 'Horizontal rule',
|
name: 'Horizontal rule',
|
||||||
icon: (editorState, onSelected) => Icon(
|
icon: (editorState, onSelected) => Icon(
|
||||||
Icons.horizontal_rule,
|
Icons.horizontal_rule,
|
||||||
color: onSelected
|
color: onSelected
|
||||||
|
@ -5,7 +5,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
SelectionMenuItem autoCompletionMenuItem = SelectionMenuItem(
|
SelectionMenuItem autoCompletionMenuItem = SelectionMenuItem(
|
||||||
name: () => 'Auto generate content',
|
name: 'Auto generate content',
|
||||||
icon: (editorState, onSelected) => Icon(
|
icon: (editorState, onSelected) => Icon(
|
||||||
Icons.rocket,
|
Icons.rocket,
|
||||||
size: 18.0,
|
size: 18.0,
|
||||||
|
@ -4,7 +4,7 @@ import 'package:example/plugin/AI/text_robot.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
SelectionMenuItem continueToWriteMenuItem = SelectionMenuItem(
|
SelectionMenuItem continueToWriteMenuItem = SelectionMenuItem(
|
||||||
name: () => 'Continue To Write',
|
name: 'Continue To Write',
|
||||||
icon: (editorState, onSelected) => Icon(
|
icon: (editorState, onSelected) => Icon(
|
||||||
Icons.print,
|
Icons.print,
|
||||||
size: 18.0,
|
size: 18.0,
|
||||||
|
@ -37,7 +37,6 @@ class BuiltInAttributeKey {
|
|||||||
static String checkbox = 'checkbox';
|
static String checkbox = 'checkbox';
|
||||||
static String code = 'code';
|
static String code = 'code';
|
||||||
static String number = 'number';
|
static String number = 'number';
|
||||||
static String defaultFormating = 'defaultFormating';
|
|
||||||
|
|
||||||
static List<String> partialStyleKeys = [
|
static List<String> partialStyleKeys = [
|
||||||
BuiltInAttributeKey.bold,
|
BuiltInAttributeKey.bold,
|
||||||
|
@ -47,7 +47,7 @@ class _SelectionMenuItemWidgetState extends State<SelectionMenuItemWidget> {
|
|||||||
: MaterialStateProperty.all(Colors.transparent),
|
: MaterialStateProperty.all(Colors.transparent),
|
||||||
),
|
),
|
||||||
label: Text(
|
label: Text(
|
||||||
widget.item.name(),
|
widget.item.name,
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: (widget.isSelected || _onHover)
|
color: (widget.isSelected || _onHover)
|
||||||
|
@ -156,7 +156,7 @@ List<SelectionMenuItem> get defaultSelectionMenuItems =>
|
|||||||
_defaultSelectionMenuItems;
|
_defaultSelectionMenuItems;
|
||||||
final List<SelectionMenuItem> _defaultSelectionMenuItems = [
|
final List<SelectionMenuItem> _defaultSelectionMenuItems = [
|
||||||
SelectionMenuItem(
|
SelectionMenuItem(
|
||||||
name: () => AppFlowyEditorLocalizations.current.text,
|
name: AppFlowyEditorLocalizations.current.text,
|
||||||
icon: (editorState, onSelected) =>
|
icon: (editorState, onSelected) =>
|
||||||
_selectionMenuIcon('text', editorState, onSelected),
|
_selectionMenuIcon('text', editorState, onSelected),
|
||||||
keywords: ['text'],
|
keywords: ['text'],
|
||||||
@ -165,7 +165,7 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
SelectionMenuItem(
|
SelectionMenuItem(
|
||||||
name: () => AppFlowyEditorLocalizations.current.heading1,
|
name: AppFlowyEditorLocalizations.current.heading1,
|
||||||
icon: (editorState, onSelected) =>
|
icon: (editorState, onSelected) =>
|
||||||
_selectionMenuIcon('h1', editorState, onSelected),
|
_selectionMenuIcon('h1', editorState, onSelected),
|
||||||
keywords: ['heading 1, h1'],
|
keywords: ['heading 1, h1'],
|
||||||
@ -174,7 +174,7 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
SelectionMenuItem(
|
SelectionMenuItem(
|
||||||
name: () => AppFlowyEditorLocalizations.current.heading2,
|
name: AppFlowyEditorLocalizations.current.heading2,
|
||||||
icon: (editorState, onSelected) =>
|
icon: (editorState, onSelected) =>
|
||||||
_selectionMenuIcon('h2', editorState, onSelected),
|
_selectionMenuIcon('h2', editorState, onSelected),
|
||||||
keywords: ['heading 2, h2'],
|
keywords: ['heading 2, h2'],
|
||||||
@ -183,7 +183,7 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
SelectionMenuItem(
|
SelectionMenuItem(
|
||||||
name: () => AppFlowyEditorLocalizations.current.heading3,
|
name: AppFlowyEditorLocalizations.current.heading3,
|
||||||
icon: (editorState, onSelected) =>
|
icon: (editorState, onSelected) =>
|
||||||
_selectionMenuIcon('h3', editorState, onSelected),
|
_selectionMenuIcon('h3', editorState, onSelected),
|
||||||
keywords: ['heading 3, h3'],
|
keywords: ['heading 3, h3'],
|
||||||
@ -192,14 +192,14 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
SelectionMenuItem(
|
SelectionMenuItem(
|
||||||
name: () => AppFlowyEditorLocalizations.current.image,
|
name: AppFlowyEditorLocalizations.current.image,
|
||||||
icon: (editorState, onSelected) =>
|
icon: (editorState, onSelected) =>
|
||||||
_selectionMenuIcon('image', editorState, onSelected),
|
_selectionMenuIcon('image', editorState, onSelected),
|
||||||
keywords: ['image'],
|
keywords: ['image'],
|
||||||
handler: showImageUploadMenu,
|
handler: showImageUploadMenu,
|
||||||
),
|
),
|
||||||
SelectionMenuItem(
|
SelectionMenuItem(
|
||||||
name: () => AppFlowyEditorLocalizations.current.bulletedList,
|
name: AppFlowyEditorLocalizations.current.bulletedList,
|
||||||
icon: (editorState, onSelected) =>
|
icon: (editorState, onSelected) =>
|
||||||
_selectionMenuIcon('bulleted_list', editorState, onSelected),
|
_selectionMenuIcon('bulleted_list', editorState, onSelected),
|
||||||
keywords: ['bulleted list', 'list', 'unordered list'],
|
keywords: ['bulleted list', 'list', 'unordered list'],
|
||||||
@ -208,7 +208,7 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
SelectionMenuItem(
|
SelectionMenuItem(
|
||||||
name: () => AppFlowyEditorLocalizations.current.numberedList,
|
name: AppFlowyEditorLocalizations.current.numberedList,
|
||||||
icon: (editorState, onSelected) =>
|
icon: (editorState, onSelected) =>
|
||||||
_selectionMenuIcon('number', editorState, onSelected),
|
_selectionMenuIcon('number', editorState, onSelected),
|
||||||
keywords: ['numbered list', 'list', 'ordered list'],
|
keywords: ['numbered list', 'list', 'ordered list'],
|
||||||
@ -217,7 +217,7 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
SelectionMenuItem(
|
SelectionMenuItem(
|
||||||
name: () => AppFlowyEditorLocalizations.current.checkbox,
|
name: AppFlowyEditorLocalizations.current.checkbox,
|
||||||
icon: (editorState, onSelected) =>
|
icon: (editorState, onSelected) =>
|
||||||
_selectionMenuIcon('checkbox', editorState, onSelected),
|
_selectionMenuIcon('checkbox', editorState, onSelected),
|
||||||
keywords: ['todo list', 'list', 'checkbox list'],
|
keywords: ['todo list', 'list', 'checkbox list'],
|
||||||
@ -226,7 +226,7 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
SelectionMenuItem(
|
SelectionMenuItem(
|
||||||
name: () => AppFlowyEditorLocalizations.current.quote,
|
name: AppFlowyEditorLocalizations.current.quote,
|
||||||
icon: (editorState, onSelected) =>
|
icon: (editorState, onSelected) =>
|
||||||
_selectionMenuIcon('quote', editorState, onSelected),
|
_selectionMenuIcon('quote', editorState, onSelected),
|
||||||
keywords: ['quote', 'refer'],
|
keywords: ['quote', 'refer'],
|
||||||
|
@ -20,14 +20,14 @@ class SelectionMenuItem {
|
|||||||
required SelectionMenuItemHandler handler,
|
required SelectionMenuItemHandler handler,
|
||||||
}) {
|
}) {
|
||||||
this.handler = (editorState, menuService, context) {
|
this.handler = (editorState, menuService, context) {
|
||||||
_deleteToSlash(editorState);
|
_deleteSlash(editorState);
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||||
handler(editorState, menuService, context);
|
handler(editorState, menuService, context);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
final String Function() name;
|
final String name;
|
||||||
final Widget Function(EditorState editorState, bool onSelected) icon;
|
final Widget Function(EditorState editorState, bool onSelected) icon;
|
||||||
|
|
||||||
/// Customizes keywords for item.
|
/// Customizes keywords for item.
|
||||||
@ -36,20 +36,23 @@ class SelectionMenuItem {
|
|||||||
final List<String> keywords;
|
final List<String> keywords;
|
||||||
late final SelectionMenuItemHandler handler;
|
late final SelectionMenuItemHandler handler;
|
||||||
|
|
||||||
void _deleteToSlash(EditorState editorState) {
|
void _deleteSlash(EditorState editorState) {
|
||||||
final selectionService = editorState.service.selectionService;
|
final selectionService = editorState.service.selectionService;
|
||||||
final selection = selectionService.currentSelection.value;
|
final selection = selectionService.currentSelection.value;
|
||||||
final nodes = selectionService.currentSelectedNodes;
|
final nodes = selectionService.currentSelectedNodes;
|
||||||
if (selection != null && nodes.length == 1) {
|
if (selection != null && nodes.length == 1) {
|
||||||
final node = nodes.first as TextNode;
|
final node = nodes.first as TextNode;
|
||||||
final end = selection.start.offset;
|
final end = selection.start.offset;
|
||||||
final start = node.toPlainText().substring(0, end).lastIndexOf('/');
|
final lastSlashIndex =
|
||||||
|
node.toPlainText().substring(0, end).lastIndexOf('/');
|
||||||
|
// delete all the texts after '/' along with '/'
|
||||||
final transaction = editorState.transaction
|
final transaction = editorState.transaction
|
||||||
..deleteText(
|
..deleteText(
|
||||||
node,
|
node,
|
||||||
start,
|
lastSlashIndex,
|
||||||
selection.start.offset - start,
|
end - lastSlashIndex,
|
||||||
);
|
);
|
||||||
|
|
||||||
editorState.apply(transaction);
|
editorState.apply(transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -81,7 +84,7 @@ class SelectionMenuItem {
|
|||||||
updateSelection,
|
updateSelection,
|
||||||
}) {
|
}) {
|
||||||
return SelectionMenuItem(
|
return SelectionMenuItem(
|
||||||
name: () => name,
|
name: name,
|
||||||
icon: (editorState, onSelected) => Icon(
|
icon: (editorState, onSelected) => Icon(
|
||||||
iconData,
|
iconData,
|
||||||
color: onSelected
|
color: onSelected
|
||||||
|
@ -265,135 +265,6 @@ ShortcutEventHandler markdownLinkOrImageHandler = (editorState, event) {
|
|||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
};
|
};
|
||||||
|
|
||||||
// convert **abc** to bold abc.
|
|
||||||
ShortcutEventHandler doubleAsterisksToBold = (editorState, event) {
|
|
||||||
final selectionService = editorState.service.selectionService;
|
|
||||||
final selection = selectionService.currentSelection.value;
|
|
||||||
final textNodes = selectionService.currentSelectedNodes.whereType<TextNode>();
|
|
||||||
if (selection == null || !selection.isSingle || textNodes.length != 1) {
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
}
|
|
||||||
|
|
||||||
final textNode = textNodes.first;
|
|
||||||
final text = textNode.toPlainText().substring(0, selection.end.offset);
|
|
||||||
|
|
||||||
// make sure the last two characters are **.
|
|
||||||
if (text.length < 2 || text[selection.end.offset - 1] != '*') {
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
}
|
|
||||||
|
|
||||||
// find all the index of `*`.
|
|
||||||
final asteriskIndexes = <int>[];
|
|
||||||
for (var i = 0; i < text.length; i++) {
|
|
||||||
if (text[i] == '*') {
|
|
||||||
asteriskIndexes.add(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (asteriskIndexes.length < 3) {
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure the second to last and third to last asterisks are connected.
|
|
||||||
final thirdToLastAsteriskIndex = asteriskIndexes[asteriskIndexes.length - 3];
|
|
||||||
final secondToLastAsteriskIndex = asteriskIndexes[asteriskIndexes.length - 2];
|
|
||||||
final lastAsterisIndex = asteriskIndexes[asteriskIndexes.length - 1];
|
|
||||||
if (secondToLastAsteriskIndex != thirdToLastAsteriskIndex + 1 ||
|
|
||||||
lastAsterisIndex == secondToLastAsteriskIndex + 1) {
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete the last three asterisks.
|
|
||||||
// update the style of the text surround by `** **` to bold.
|
|
||||||
// and update the cursor position.
|
|
||||||
final transaction = editorState.transaction
|
|
||||||
..deleteText(textNode, lastAsterisIndex, 1)
|
|
||||||
..deleteText(textNode, thirdToLastAsteriskIndex, 2)
|
|
||||||
..formatText(
|
|
||||||
textNode,
|
|
||||||
thirdToLastAsteriskIndex,
|
|
||||||
selection.end.offset - thirdToLastAsteriskIndex - 3,
|
|
||||||
{
|
|
||||||
BuiltInAttributeKey.bold: true,
|
|
||||||
BuiltInAttributeKey.defaultFormating: true,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
..afterSelection = Selection.collapsed(
|
|
||||||
Position(
|
|
||||||
path: textNode.path,
|
|
||||||
offset: selection.end.offset - 3,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
editorState.apply(transaction);
|
|
||||||
|
|
||||||
return KeyEventResult.handled;
|
|
||||||
};
|
|
||||||
|
|
||||||
// convert __abc__ to bold abc.
|
|
||||||
ShortcutEventHandler doubleUnderscoresToBold = (editorState, event) {
|
|
||||||
final selectionService = editorState.service.selectionService;
|
|
||||||
final selection = selectionService.currentSelection.value;
|
|
||||||
final textNodes = selectionService.currentSelectedNodes.whereType<TextNode>();
|
|
||||||
if (selection == null || !selection.isSingle || textNodes.length != 1) {
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
}
|
|
||||||
|
|
||||||
final textNode = textNodes.first;
|
|
||||||
final text = textNode.toPlainText().substring(0, selection.end.offset);
|
|
||||||
|
|
||||||
// make sure the last two characters are __.
|
|
||||||
if (text.length < 2 || text[selection.end.offset - 1] != '_') {
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
}
|
|
||||||
|
|
||||||
// find all the index of `_`.
|
|
||||||
final underscoreIndexes = <int>[];
|
|
||||||
for (var i = 0; i < text.length; i++) {
|
|
||||||
if (text[i] == '_') {
|
|
||||||
underscoreIndexes.add(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (underscoreIndexes.length < 3) {
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure the second to last and third to last underscores are connected.
|
|
||||||
final thirdToLastUnderscoreIndex =
|
|
||||||
underscoreIndexes[underscoreIndexes.length - 3];
|
|
||||||
final secondToLastUnderscoreIndex =
|
|
||||||
underscoreIndexes[underscoreIndexes.length - 2];
|
|
||||||
final lastAsterisIndex = underscoreIndexes[underscoreIndexes.length - 1];
|
|
||||||
if (secondToLastUnderscoreIndex != thirdToLastUnderscoreIndex + 1 ||
|
|
||||||
lastAsterisIndex == secondToLastUnderscoreIndex + 1) {
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete the last three underscores.
|
|
||||||
// update the style of the text surround by `__ __` to bold.
|
|
||||||
// and update the cursor position.
|
|
||||||
final transaction = editorState.transaction
|
|
||||||
..deleteText(textNode, lastAsterisIndex, 1)
|
|
||||||
..deleteText(textNode, thirdToLastUnderscoreIndex, 2)
|
|
||||||
..formatText(
|
|
||||||
textNode,
|
|
||||||
thirdToLastUnderscoreIndex,
|
|
||||||
selection.end.offset - thirdToLastUnderscoreIndex - 3,
|
|
||||||
{
|
|
||||||
BuiltInAttributeKey.bold: true,
|
|
||||||
BuiltInAttributeKey.defaultFormating: true,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
..afterSelection = Selection.collapsed(
|
|
||||||
Position(
|
|
||||||
path: textNode.path,
|
|
||||||
offset: selection.end.offset - 3,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
editorState.apply(transaction);
|
|
||||||
return KeyEventResult.handled;
|
|
||||||
};
|
|
||||||
|
|
||||||
ShortcutEventHandler underscoreToItalicHandler = (editorState, event) {
|
ShortcutEventHandler underscoreToItalicHandler = (editorState, event) {
|
||||||
// Obtain the selection and selected nodes of the current document through the 'selectionService'
|
// Obtain the selection and selected nodes of the current document through the 'selectionService'
|
||||||
// to determine whether the selection is collapsed and whether the selected node is a text node.
|
// to determine whether the selection is collapsed and whether the selected node is a text node.
|
||||||
@ -438,3 +309,117 @@ ShortcutEventHandler underscoreToItalicHandler = (editorState, event) {
|
|||||||
|
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ShortcutEventHandler doubleAsteriskToBoldHanlder = (editorState, event) {
|
||||||
|
final selectionService = editorState.service.selectionService;
|
||||||
|
final selection = selectionService.currentSelection.value;
|
||||||
|
final textNodes = selectionService.currentSelectedNodes.whereType<TextNode>();
|
||||||
|
|
||||||
|
if (selection == null || !selection.isSingle || textNodes.length != 1) {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
|
||||||
|
final textNode = textNodes.first;
|
||||||
|
final text = textNode.toPlainText();
|
||||||
|
|
||||||
|
// make sure the last two characters are '**'
|
||||||
|
if (text.length < 2 || text[selection.end.offset - 1] != '*') {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find all the index of '*'
|
||||||
|
final asteriskIndexList = <int>[];
|
||||||
|
for (var i = 0; i < text.length; i++) {
|
||||||
|
if (text[i] == '*') {
|
||||||
|
asteriskIndexList.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (asteriskIndexList.length < 3) return KeyEventResult.ignored;
|
||||||
|
|
||||||
|
// make sure the second to last and third to last asterisk are connected
|
||||||
|
final thirdToLastAsteriskIndex =
|
||||||
|
asteriskIndexList[asteriskIndexList.length - 3];
|
||||||
|
final secondToLastAsteriskIndex =
|
||||||
|
asteriskIndexList[asteriskIndexList.length - 2];
|
||||||
|
final lastAsteriskIndex = asteriskIndexList[asteriskIndexList.length - 1];
|
||||||
|
if (secondToLastAsteriskIndex != thirdToLastAsteriskIndex + 1 ||
|
||||||
|
lastAsteriskIndex == secondToLastAsteriskIndex + 1) {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
|
||||||
|
//delete the last three asterisks
|
||||||
|
//update the style of the text surround by '** **' to bold
|
||||||
|
//update the cursor position
|
||||||
|
final transaction = editorState.transaction
|
||||||
|
..deleteText(textNode, lastAsteriskIndex, 1)
|
||||||
|
..deleteText(textNode, thirdToLastAsteriskIndex, 2)
|
||||||
|
..formatText(textNode, thirdToLastAsteriskIndex,
|
||||||
|
selection.end.offset - thirdToLastAsteriskIndex - 2, {
|
||||||
|
BuiltInAttributeKey.bold: true,
|
||||||
|
})
|
||||||
|
..afterSelection = Selection.collapsed(
|
||||||
|
Position(path: textNode.path, offset: selection.end.offset - 3));
|
||||||
|
|
||||||
|
editorState.apply(transaction);
|
||||||
|
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
};
|
||||||
|
|
||||||
|
//Implement in the same way as doubleAsteriskToBoldHanlder
|
||||||
|
ShortcutEventHandler doubleUnderscoreToBoldHanlder = (editorState, event) {
|
||||||
|
final selectionService = editorState.service.selectionService;
|
||||||
|
final selection = selectionService.currentSelection.value;
|
||||||
|
final textNodes = selectionService.currentSelectedNodes.whereType<TextNode>();
|
||||||
|
|
||||||
|
if (selection == null || !selection.isSingle || textNodes.length != 1) {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
|
||||||
|
final textNode = textNodes.first;
|
||||||
|
final text = textNode.toPlainText();
|
||||||
|
|
||||||
|
// make sure the last two characters are '__'
|
||||||
|
if (text.length < 2 || text[selection.end.offset - 1] != '_') {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find all the index of '_'
|
||||||
|
final underscoreIndexList = <int>[];
|
||||||
|
for (var i = 0; i < text.length; i++) {
|
||||||
|
if (text[i] == '_') {
|
||||||
|
underscoreIndexList.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (underscoreIndexList.length < 3) return KeyEventResult.ignored;
|
||||||
|
|
||||||
|
// make sure the second to last and third to last underscore are connected
|
||||||
|
final thirdToLastUnderscoreIndex =
|
||||||
|
underscoreIndexList[underscoreIndexList.length - 3];
|
||||||
|
final secondToLastUnderscoreIndex =
|
||||||
|
underscoreIndexList[underscoreIndexList.length - 2];
|
||||||
|
final lastUnderscoreIndex =
|
||||||
|
underscoreIndexList[underscoreIndexList.length - 1];
|
||||||
|
if (secondToLastUnderscoreIndex != thirdToLastUnderscoreIndex + 1 ||
|
||||||
|
lastUnderscoreIndex == secondToLastUnderscoreIndex + 1) {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
|
||||||
|
//delete the last three underscores
|
||||||
|
//update the style of the text surround by '__ __' to bold
|
||||||
|
//update the cursor position
|
||||||
|
final transaction = editorState.transaction
|
||||||
|
..deleteText(textNode, lastUnderscoreIndex, 1)
|
||||||
|
..deleteText(textNode, thirdToLastUnderscoreIndex, 2)
|
||||||
|
..formatText(textNode, thirdToLastUnderscoreIndex,
|
||||||
|
selection.end.offset - thirdToLastUnderscoreIndex - 2, {
|
||||||
|
BuiltInAttributeKey.bold: true,
|
||||||
|
})
|
||||||
|
..afterSelection = Selection.collapsed(
|
||||||
|
Position(path: textNode.path, offset: selection.end.offset - 3));
|
||||||
|
|
||||||
|
editorState.apply(transaction);
|
||||||
|
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
};
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
// List<>
|
|
||||||
|
|
||||||
import 'package:appflowy_editor/src/service/internal_key_event_handlers/arrow_keys_handler.dart';
|
import 'package:appflowy_editor/src/service/internal_key_event_handlers/arrow_keys_handler.dart';
|
||||||
import 'package:appflowy_editor/src/service/internal_key_event_handlers/backspace_handler.dart';
|
import 'package:appflowy_editor/src/service/internal_key_event_handlers/backspace_handler.dart';
|
||||||
import 'package:appflowy_editor/src/service/internal_key_event_handlers/copy_paste_handler.dart';
|
import 'package:appflowy_editor/src/service/internal_key_event_handlers/copy_paste_handler.dart';
|
||||||
@ -284,16 +282,6 @@ List<ShortcutEvent> builtInShortcutEvents = [
|
|||||||
command: 'tab',
|
command: 'tab',
|
||||||
handler: tabHandler,
|
handler: tabHandler,
|
||||||
),
|
),
|
||||||
ShortcutEvent(
|
|
||||||
key: 'Double stars to bold',
|
|
||||||
command: 'shift+asterisk',
|
|
||||||
handler: doubleAsterisksToBold,
|
|
||||||
),
|
|
||||||
ShortcutEvent(
|
|
||||||
key: 'Double underscores to bold',
|
|
||||||
command: 'shift+underscore',
|
|
||||||
handler: doubleUnderscoresToBold,
|
|
||||||
),
|
|
||||||
ShortcutEvent(
|
ShortcutEvent(
|
||||||
key: 'Backquote to code',
|
key: 'Backquote to code',
|
||||||
command: 'backquote',
|
command: 'backquote',
|
||||||
@ -319,6 +307,16 @@ List<ShortcutEvent> builtInShortcutEvents = [
|
|||||||
command: 'shift+underscore',
|
command: 'shift+underscore',
|
||||||
handler: underscoreToItalicHandler,
|
handler: underscoreToItalicHandler,
|
||||||
),
|
),
|
||||||
|
ShortcutEvent(
|
||||||
|
key: 'Double asterisk to bold',
|
||||||
|
command: 'shift+digit 8',
|
||||||
|
handler: doubleAsteriskToBoldHanlder,
|
||||||
|
),
|
||||||
|
ShortcutEvent(
|
||||||
|
key: 'Double underscore to bold',
|
||||||
|
command: 'shift+underscore',
|
||||||
|
handler: doubleUnderscoreToBoldHanlder,
|
||||||
|
),
|
||||||
// https://github.com/flutter/flutter/issues/104944
|
// https://github.com/flutter/flutter/issues/104944
|
||||||
// Workaround: Using space editing on the web platform often results in errors,
|
// Workaround: Using space editing on the web platform often results in errors,
|
||||||
// so adding a shortcut event to handle the space input instead of using the
|
// so adding a shortcut event to handle the space input instead of using the
|
||||||
|
@ -142,15 +142,15 @@ extension on LogicalKeyboardKey {
|
|||||||
if (this == LogicalKeyboardKey.keyZ) {
|
if (this == LogicalKeyboardKey.keyZ) {
|
||||||
return PhysicalKeyboardKey.keyZ;
|
return PhysicalKeyboardKey.keyZ;
|
||||||
}
|
}
|
||||||
if (this == LogicalKeyboardKey.asterisk) {
|
if (this == LogicalKeyboardKey.tilde) {
|
||||||
|
return PhysicalKeyboardKey.backquote;
|
||||||
|
}
|
||||||
|
if (this == LogicalKeyboardKey.digit8) {
|
||||||
return PhysicalKeyboardKey.digit8;
|
return PhysicalKeyboardKey.digit8;
|
||||||
}
|
}
|
||||||
if (this == LogicalKeyboardKey.underscore) {
|
if (this == LogicalKeyboardKey.underscore) {
|
||||||
return PhysicalKeyboardKey.minus;
|
return PhysicalKeyboardKey.minus;
|
||||||
}
|
}
|
||||||
if (this == LogicalKeyboardKey.tilde) {
|
|
||||||
return PhysicalKeyboardKey.backquote;
|
|
||||||
}
|
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ void main() async {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// https://github.com/AppFlowy-IO/AppFlowy/issues/1763
|
// https://github.com/AppFlowy-IO/AppFlowy/issues/1763
|
||||||
// [Bug] Mouse unable to click a certain area #1763
|
// // [Bug] Mouse unable to click a certain area #1763
|
||||||
testWidgets('insert a new checkbox after an exsiting checkbox',
|
testWidgets('insert a new checkbox after an exsiting checkbox',
|
||||||
(tester) async {
|
(tester) async {
|
||||||
// Before
|
// Before
|
||||||
|
@ -10,40 +10,44 @@ void main() async {
|
|||||||
});
|
});
|
||||||
|
|
||||||
group('selection_menu_widget.dart', () {
|
group('selection_menu_widget.dart', () {
|
||||||
for (var i = 0; i < defaultSelectionMenuItems.length; i += 1) {
|
// const i = defaultSelectionMenuItems.length;
|
||||||
testWidgets('Selects number.$i item in selection menu with enter',
|
//
|
||||||
(tester) async {
|
// Because the `defaultSelectionMenuItems` uses localization,
|
||||||
final editor = await _prepare(tester);
|
// and the MaterialApp has not been initialized at the time of getting the value,
|
||||||
for (var j = 0; j < i; j++) {
|
// it will crash.
|
||||||
await editor.pressLogicKey(LogicalKeyboardKey.arrowDown);
|
//
|
||||||
}
|
// Use const value temporarily instead.
|
||||||
|
const i = 7;
|
||||||
|
testWidgets('Selects number.$i item in selection menu with keyboard',
|
||||||
|
(tester) async {
|
||||||
|
final editor = await _prepare(tester);
|
||||||
|
for (var j = 0; j < i; j++) {
|
||||||
|
await editor.pressLogicKey(LogicalKeyboardKey.arrowDown);
|
||||||
|
}
|
||||||
|
|
||||||
await editor.pressLogicKey(LogicalKeyboardKey.enter);
|
await editor.pressLogicKey(LogicalKeyboardKey.enter);
|
||||||
expect(
|
expect(
|
||||||
find.byType(SelectionMenuWidget, skipOffstage: false),
|
find.byType(SelectionMenuWidget, skipOffstage: false),
|
||||||
findsNothing,
|
findsNothing,
|
||||||
);
|
);
|
||||||
if (defaultSelectionMenuItems[i].name() != 'Image') {
|
if (defaultSelectionMenuItems[i].name != 'Image') {
|
||||||
await _testDefaultSelectionMenuItems(i, editor);
|
await _testDefaultSelectionMenuItems(i, editor);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Selects number.$i item in selection menu with click',
|
testWidgets('Selects number.$i item in selection menu with clicking',
|
||||||
(tester) async {
|
(tester) async {
|
||||||
final editor = await _prepare(tester);
|
final editor = await _prepare(tester);
|
||||||
|
await tester.tap(find.byType(SelectionMenuItemWidget).at(i));
|
||||||
await tester.tap(find.byType(SelectionMenuItemWidget).at(i));
|
await tester.pumpAndSettle();
|
||||||
await tester.pumpAndSettle();
|
expect(
|
||||||
|
find.byType(SelectionMenuWidget, skipOffstage: false),
|
||||||
expect(
|
findsNothing,
|
||||||
find.byType(SelectionMenuWidget, skipOffstage: false),
|
);
|
||||||
findsNothing,
|
if (defaultSelectionMenuItems[i].name != 'Image') {
|
||||||
);
|
await _testDefaultSelectionMenuItems(i, editor);
|
||||||
if (defaultSelectionMenuItems[i].name() != 'Image') {
|
}
|
||||||
await _testDefaultSelectionMenuItems(i, editor);
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
testWidgets('Search item in selection menu util no results',
|
testWidgets('Search item in selection menu util no results',
|
||||||
(tester) async {
|
(tester) async {
|
||||||
@ -136,7 +140,7 @@ Future<EditorWidgetTester> _prepare(WidgetTester tester) async {
|
|||||||
);
|
);
|
||||||
|
|
||||||
for (final item in defaultSelectionMenuItems) {
|
for (final item in defaultSelectionMenuItems) {
|
||||||
expect(find.text(item.name()), findsOneWidget);
|
expect(find.text(item.name), findsOneWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Future.value(editor);
|
return Future.value(editor);
|
||||||
@ -146,28 +150,31 @@ Future<void> _testDefaultSelectionMenuItems(
|
|||||||
int index, EditorWidgetTester editor) async {
|
int index, EditorWidgetTester editor) async {
|
||||||
expect(editor.documentLength, 4);
|
expect(editor.documentLength, 4);
|
||||||
expect(editor.documentSelection, Selection.single(path: [2], startOffset: 0));
|
expect(editor.documentSelection, Selection.single(path: [2], startOffset: 0));
|
||||||
|
expect((editor.nodeAtPath([0]) as TextNode).toPlainText(),
|
||||||
|
'Welcome to Appflowy 😁');
|
||||||
expect((editor.nodeAtPath([1]) as TextNode).toPlainText(),
|
expect((editor.nodeAtPath([1]) as TextNode).toPlainText(),
|
||||||
'Welcome to Appflowy 😁');
|
'Welcome to Appflowy 😁');
|
||||||
final node = editor.nodeAtPath([2]);
|
final node = editor.nodeAtPath([2]);
|
||||||
final item = defaultSelectionMenuItems[index];
|
final item = defaultSelectionMenuItems[index];
|
||||||
final itemName = item.name();
|
if (item.name == 'Text') {
|
||||||
if (itemName == 'Text') {
|
|
||||||
expect(node?.subtype == null, true);
|
expect(node?.subtype == null, true);
|
||||||
} else if (itemName == 'Heading 1') {
|
expect(node?.toString(), null);
|
||||||
|
} else if (item.name == 'Heading 1') {
|
||||||
expect(node?.subtype, BuiltInAttributeKey.heading);
|
expect(node?.subtype, BuiltInAttributeKey.heading);
|
||||||
expect(node?.attributes.heading, BuiltInAttributeKey.h1);
|
expect(node?.attributes.heading, BuiltInAttributeKey.h1);
|
||||||
} else if (itemName == 'Heading 2') {
|
expect(node?.toString(), null);
|
||||||
|
} else if (item.name == 'Heading 2') {
|
||||||
expect(node?.subtype, BuiltInAttributeKey.heading);
|
expect(node?.subtype, BuiltInAttributeKey.heading);
|
||||||
expect(node?.attributes.heading, BuiltInAttributeKey.h2);
|
expect(node?.attributes.heading, BuiltInAttributeKey.h2);
|
||||||
} else if (itemName == 'Heading 3') {
|
expect(node?.toString(), null);
|
||||||
|
} else if (item.name == 'Heading 3') {
|
||||||
expect(node?.subtype, BuiltInAttributeKey.heading);
|
expect(node?.subtype, BuiltInAttributeKey.heading);
|
||||||
expect(node?.attributes.heading, BuiltInAttributeKey.h3);
|
expect(node?.attributes.heading, BuiltInAttributeKey.h3);
|
||||||
} else if (itemName == 'Bulleted list') {
|
expect(node?.toString(), null);
|
||||||
|
} else if (item.name == 'Bulleted list') {
|
||||||
expect(node?.subtype, BuiltInAttributeKey.bulletedList);
|
expect(node?.subtype, BuiltInAttributeKey.bulletedList);
|
||||||
} else if (itemName == 'Checkbox') {
|
} else if (item.name == 'Checkbox') {
|
||||||
expect(node?.subtype, BuiltInAttributeKey.checkbox);
|
expect(node?.subtype, BuiltInAttributeKey.checkbox);
|
||||||
expect(node?.attributes.check, false);
|
expect(node?.attributes.check, false);
|
||||||
} else if (itemName == 'Quote') {
|
|
||||||
expect(node?.subtype, BuiltInAttributeKey.quote);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,277 +0,0 @@
|
|||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
|
||||||
import 'package:appflowy_editor/src/extensions/text_node_extensions.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import '../../infra/test_editor.dart';
|
|
||||||
|
|
||||||
void main() async {
|
|
||||||
setUpAll(() {
|
|
||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
|
||||||
});
|
|
||||||
|
|
||||||
group('markdown_syntax_to_styled_text_handler.dart', () {
|
|
||||||
group('convert double asterisks to bold', () {
|
|
||||||
Future<void> insertAsterisk(
|
|
||||||
EditorWidgetTester editor, {
|
|
||||||
int repeat = 1,
|
|
||||||
}) async {
|
|
||||||
for (var i = 0; i < repeat; i++) {
|
|
||||||
await editor.pressLogicKey(
|
|
||||||
LogicalKeyboardKey.asterisk,
|
|
||||||
isShiftPressed: true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
testWidgets('**AppFlowy** to bold AppFlowy', (tester) async {
|
|
||||||
const text = '**AppFlowy*';
|
|
||||||
final editor = tester.editor..insertTextNode('');
|
|
||||||
await editor.startTesting();
|
|
||||||
await editor.updateSelection(
|
|
||||||
Selection.single(path: [0], startOffset: 0),
|
|
||||||
);
|
|
||||||
final textNode = editor.nodeAtPath([0]) as TextNode;
|
|
||||||
for (var i = 0; i < text.length; i++) {
|
|
||||||
await editor.insertText(textNode, text[i], i);
|
|
||||||
}
|
|
||||||
await insertAsterisk(editor);
|
|
||||||
final allBold = textNode.allSatisfyBoldInSelection(
|
|
||||||
Selection.single(
|
|
||||||
path: [0],
|
|
||||||
startOffset: 0,
|
|
||||||
endOffset: textNode.toPlainText().length,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
expect(allBold, true);
|
|
||||||
expect(textNode.toPlainText(), 'AppFlowy');
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('App**Flowy** to bold AppFlowy', (tester) async {
|
|
||||||
const text = 'App**Flowy*';
|
|
||||||
final editor = tester.editor..insertTextNode('');
|
|
||||||
await editor.startTesting();
|
|
||||||
await editor.updateSelection(
|
|
||||||
Selection.single(path: [0], startOffset: 0),
|
|
||||||
);
|
|
||||||
final textNode = editor.nodeAtPath([0]) as TextNode;
|
|
||||||
for (var i = 0; i < text.length; i++) {
|
|
||||||
await editor.insertText(textNode, text[i], i);
|
|
||||||
}
|
|
||||||
await insertAsterisk(editor);
|
|
||||||
final allBold = textNode.allSatisfyBoldInSelection(
|
|
||||||
Selection.single(
|
|
||||||
path: [0],
|
|
||||||
startOffset: 3,
|
|
||||||
endOffset: textNode.toPlainText().length,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
expect(allBold, true);
|
|
||||||
expect(textNode.toPlainText(), 'AppFlowy');
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('***AppFlowy** to bold *AppFlowy', (tester) async {
|
|
||||||
const text = '***AppFlowy*';
|
|
||||||
final editor = tester.editor..insertTextNode('');
|
|
||||||
await editor.startTesting();
|
|
||||||
await editor.updateSelection(
|
|
||||||
Selection.single(path: [0], startOffset: 0),
|
|
||||||
);
|
|
||||||
final textNode = editor.nodeAtPath([0]) as TextNode;
|
|
||||||
for (var i = 0; i < text.length; i++) {
|
|
||||||
await editor.insertText(textNode, text[i], i);
|
|
||||||
}
|
|
||||||
await insertAsterisk(editor);
|
|
||||||
final allBold = textNode.allSatisfyBoldInSelection(
|
|
||||||
Selection.single(
|
|
||||||
path: [0],
|
|
||||||
startOffset: 1,
|
|
||||||
endOffset: textNode.toPlainText().length,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
expect(allBold, true);
|
|
||||||
expect(textNode.toPlainText(), '*AppFlowy');
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('**AppFlowy** application to bold AppFlowy only',
|
|
||||||
(tester) async {
|
|
||||||
const boldText = '**AppFlowy*';
|
|
||||||
final editor = tester.editor..insertTextNode('');
|
|
||||||
await editor.startTesting();
|
|
||||||
await editor.updateSelection(
|
|
||||||
Selection.single(path: [0], startOffset: 0),
|
|
||||||
);
|
|
||||||
final textNode = editor.nodeAtPath([0]) as TextNode;
|
|
||||||
|
|
||||||
for (var i = 0; i < boldText.length; i++) {
|
|
||||||
await editor.insertText(textNode, boldText[i], i);
|
|
||||||
}
|
|
||||||
await insertAsterisk(editor);
|
|
||||||
final boldTextLength = boldText.replaceAll('*', '').length;
|
|
||||||
final appFlowyBold = textNode.allSatisfyBoldInSelection(
|
|
||||||
Selection.single(
|
|
||||||
path: [0],
|
|
||||||
startOffset: 0,
|
|
||||||
endOffset: boldTextLength,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
expect(appFlowyBold, true);
|
|
||||||
expect(textNode.toPlainText(), 'AppFlowy');
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('**** nothing changes', (tester) async {
|
|
||||||
const text = '***';
|
|
||||||
final editor = tester.editor..insertTextNode('');
|
|
||||||
await editor.startTesting();
|
|
||||||
await editor.updateSelection(
|
|
||||||
Selection.single(path: [0], startOffset: 0),
|
|
||||||
);
|
|
||||||
final textNode = editor.nodeAtPath([0]) as TextNode;
|
|
||||||
for (var i = 0; i < text.length; i++) {
|
|
||||||
await editor.insertText(textNode, text[i], i);
|
|
||||||
}
|
|
||||||
await insertAsterisk(editor);
|
|
||||||
final allBold = textNode.allSatisfyBoldInSelection(
|
|
||||||
Selection.single(
|
|
||||||
path: [0],
|
|
||||||
startOffset: 0,
|
|
||||||
endOffset: textNode.toPlainText().length,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
expect(allBold, false);
|
|
||||||
expect(textNode.toPlainText(), text);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
group('convert double underscores to bold', () {
|
|
||||||
Future<void> insertUnderscore(
|
|
||||||
EditorWidgetTester editor, {
|
|
||||||
int repeat = 1,
|
|
||||||
}) async {
|
|
||||||
for (var i = 0; i < repeat; i++) {
|
|
||||||
await editor.pressLogicKey(
|
|
||||||
LogicalKeyboardKey.underscore,
|
|
||||||
isShiftPressed: true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
testWidgets('__AppFlowy__ to bold AppFlowy', (tester) async {
|
|
||||||
const text = '__AppFlowy_';
|
|
||||||
final editor = tester.editor..insertTextNode('');
|
|
||||||
await editor.startTesting();
|
|
||||||
await editor.updateSelection(
|
|
||||||
Selection.single(path: [0], startOffset: 0),
|
|
||||||
);
|
|
||||||
final textNode = editor.nodeAtPath([0]) as TextNode;
|
|
||||||
for (var i = 0; i < text.length; i++) {
|
|
||||||
await editor.insertText(textNode, text[i], i);
|
|
||||||
}
|
|
||||||
await insertUnderscore(editor);
|
|
||||||
final allBold = textNode.allSatisfyBoldInSelection(
|
|
||||||
Selection.single(
|
|
||||||
path: [0],
|
|
||||||
startOffset: 0,
|
|
||||||
endOffset: textNode.toPlainText().length,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
expect(allBold, true);
|
|
||||||
expect(textNode.toPlainText(), 'AppFlowy');
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('App__Flowy__ to bold AppFlowy', (tester) async {
|
|
||||||
const text = 'App__Flowy_';
|
|
||||||
final editor = tester.editor..insertTextNode('');
|
|
||||||
await editor.startTesting();
|
|
||||||
await editor.updateSelection(
|
|
||||||
Selection.single(path: [0], startOffset: 0),
|
|
||||||
);
|
|
||||||
final textNode = editor.nodeAtPath([0]) as TextNode;
|
|
||||||
for (var i = 0; i < text.length; i++) {
|
|
||||||
await editor.insertText(textNode, text[i], i);
|
|
||||||
}
|
|
||||||
await insertUnderscore(editor);
|
|
||||||
final allBold = textNode.allSatisfyBoldInSelection(
|
|
||||||
Selection.single(
|
|
||||||
path: [0],
|
|
||||||
startOffset: 3,
|
|
||||||
endOffset: textNode.toPlainText().length,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
expect(allBold, true);
|
|
||||||
expect(textNode.toPlainText(), 'AppFlowy');
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('___AppFlowy__ to bold _AppFlowy', (tester) async {
|
|
||||||
const text = '___AppFlowy_';
|
|
||||||
final editor = tester.editor..insertTextNode('');
|
|
||||||
await editor.startTesting();
|
|
||||||
await editor.updateSelection(
|
|
||||||
Selection.single(path: [0], startOffset: 0),
|
|
||||||
);
|
|
||||||
final textNode = editor.nodeAtPath([0]) as TextNode;
|
|
||||||
for (var i = 0; i < text.length; i++) {
|
|
||||||
await editor.insertText(textNode, text[i], i);
|
|
||||||
}
|
|
||||||
await insertUnderscore(editor);
|
|
||||||
final allBold = textNode.allSatisfyBoldInSelection(
|
|
||||||
Selection.single(
|
|
||||||
path: [0],
|
|
||||||
startOffset: 1,
|
|
||||||
endOffset: textNode.toPlainText().length,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
expect(allBold, true);
|
|
||||||
expect(textNode.toPlainText(), '_AppFlowy');
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('__AppFlowy__ application to bold AppFlowy only',
|
|
||||||
(tester) async {
|
|
||||||
const boldText = '__AppFlowy_';
|
|
||||||
final editor = tester.editor..insertTextNode('');
|
|
||||||
await editor.startTesting();
|
|
||||||
await editor.updateSelection(
|
|
||||||
Selection.single(path: [0], startOffset: 0),
|
|
||||||
);
|
|
||||||
final textNode = editor.nodeAtPath([0]) as TextNode;
|
|
||||||
|
|
||||||
for (var i = 0; i < boldText.length; i++) {
|
|
||||||
await editor.insertText(textNode, boldText[i], i);
|
|
||||||
}
|
|
||||||
await insertUnderscore(editor);
|
|
||||||
final boldTextLength = boldText.replaceAll('_', '').length;
|
|
||||||
final appFlowyBold = textNode.allSatisfyBoldInSelection(
|
|
||||||
Selection.single(
|
|
||||||
path: [0],
|
|
||||||
startOffset: 0,
|
|
||||||
endOffset: boldTextLength,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
expect(appFlowyBold, true);
|
|
||||||
expect(textNode.toPlainText(), 'AppFlowy');
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('____ nothing changes', (tester) async {
|
|
||||||
const text = '___';
|
|
||||||
final editor = tester.editor..insertTextNode('');
|
|
||||||
await editor.startTesting();
|
|
||||||
await editor.updateSelection(
|
|
||||||
Selection.single(path: [0], startOffset: 0),
|
|
||||||
);
|
|
||||||
final textNode = editor.nodeAtPath([0]) as TextNode;
|
|
||||||
for (var i = 0; i < text.length; i++) {
|
|
||||||
await editor.insertText(textNode, text[i], i);
|
|
||||||
}
|
|
||||||
await insertUnderscore(editor);
|
|
||||||
final allBold = textNode.allSatisfyBoldInSelection(
|
|
||||||
Selection.single(
|
|
||||||
path: [0],
|
|
||||||
startOffset: 0,
|
|
||||||
endOffset: textNode.toPlainText().length,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
expect(allBold, false);
|
|
||||||
expect(textNode.toPlainText(), text);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
@ -257,4 +257,192 @@ void main() async {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('convert double asterisk to bold', () {
|
||||||
|
Future<void> insertAsterisk(
|
||||||
|
EditorWidgetTester editor, {
|
||||||
|
int repeat = 1,
|
||||||
|
}) async {
|
||||||
|
for (var i = 0; i < repeat; i++) {
|
||||||
|
await editor.pressLogicKey(
|
||||||
|
LogicalKeyboardKey.digit8,
|
||||||
|
isShiftPressed: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testWidgets('**AppFlowy** to bold AppFlowy', ((widgetTester) async {
|
||||||
|
const text = '**AppFlowy*';
|
||||||
|
final editor = widgetTester.editor..insertTextNode('');
|
||||||
|
|
||||||
|
await editor.startTesting();
|
||||||
|
await editor.updateSelection(Selection.single(path: [0], startOffset: 0));
|
||||||
|
final textNode = editor.nodeAtPath([0]) as TextNode;
|
||||||
|
for (var i = 0; i < text.length; i++) {
|
||||||
|
await editor.insertText(textNode, text[i], i);
|
||||||
|
}
|
||||||
|
|
||||||
|
await insertAsterisk(editor);
|
||||||
|
|
||||||
|
final allBold = textNode.allSatisfyBoldInSelection(Selection.single(
|
||||||
|
path: [0], startOffset: 0, endOffset: textNode.toPlainText().length));
|
||||||
|
|
||||||
|
expect(allBold, true);
|
||||||
|
expect(textNode.toPlainText(), 'AppFlowy');
|
||||||
|
}));
|
||||||
|
|
||||||
|
testWidgets('App**Flowy** to bold AppFlowy', ((widgetTester) async {
|
||||||
|
const text = 'App**Flowy*';
|
||||||
|
final editor = widgetTester.editor..insertTextNode('');
|
||||||
|
|
||||||
|
await editor.startTesting();
|
||||||
|
await editor.updateSelection(Selection.single(path: [0], startOffset: 0));
|
||||||
|
final textNode = editor.nodeAtPath([0]) as TextNode;
|
||||||
|
for (var i = 0; i < text.length; i++) {
|
||||||
|
await editor.insertText(textNode, text[i], i);
|
||||||
|
}
|
||||||
|
|
||||||
|
await insertAsterisk(editor);
|
||||||
|
|
||||||
|
final allBold = textNode.allSatisfyBoldInSelection(Selection.single(
|
||||||
|
path: [0], startOffset: 3, endOffset: textNode.toPlainText().length));
|
||||||
|
|
||||||
|
expect(allBold, true);
|
||||||
|
expect(textNode.toPlainText(), 'AppFlowy');
|
||||||
|
}));
|
||||||
|
|
||||||
|
testWidgets('***AppFlowy** to bold *AppFlowy', ((widgetTester) async {
|
||||||
|
const text = '***AppFlowy*';
|
||||||
|
final editor = widgetTester.editor..insertTextNode('');
|
||||||
|
|
||||||
|
await editor.startTesting();
|
||||||
|
await editor.updateSelection(Selection.single(path: [0], startOffset: 0));
|
||||||
|
final textNode = editor.nodeAtPath([0]) as TextNode;
|
||||||
|
for (var i = 0; i < text.length; i++) {
|
||||||
|
await editor.insertText(textNode, text[i], i);
|
||||||
|
}
|
||||||
|
|
||||||
|
await insertAsterisk(editor);
|
||||||
|
|
||||||
|
final allBold = textNode.allSatisfyBoldInSelection(Selection.single(
|
||||||
|
path: [0], startOffset: 1, endOffset: textNode.toPlainText().length));
|
||||||
|
|
||||||
|
expect(allBold, true);
|
||||||
|
expect(textNode.toPlainText(), '*AppFlowy');
|
||||||
|
}));
|
||||||
|
|
||||||
|
testWidgets('**** nothing changes', ((widgetTester) async {
|
||||||
|
const text = '***';
|
||||||
|
final editor = widgetTester.editor..insertTextNode('');
|
||||||
|
|
||||||
|
await editor.startTesting();
|
||||||
|
await editor.updateSelection(Selection.single(path: [0], startOffset: 0));
|
||||||
|
final textNode = editor.nodeAtPath([0]) as TextNode;
|
||||||
|
for (var i = 0; i < text.length; i++) {
|
||||||
|
await editor.insertText(textNode, text[i], i);
|
||||||
|
}
|
||||||
|
|
||||||
|
await insertAsterisk(editor);
|
||||||
|
|
||||||
|
final allBold = textNode.allSatisfyBoldInSelection(Selection.single(
|
||||||
|
path: [0], startOffset: 0, endOffset: textNode.toPlainText().length));
|
||||||
|
|
||||||
|
expect(allBold, false);
|
||||||
|
expect(textNode.toPlainText(), text);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
group('convert double underscore to bold', () {
|
||||||
|
Future<void> insertUnderscore(
|
||||||
|
EditorWidgetTester editor, {
|
||||||
|
int repeat = 1,
|
||||||
|
}) async {
|
||||||
|
for (var i = 0; i < repeat; i++) {
|
||||||
|
await editor.pressLogicKey(
|
||||||
|
LogicalKeyboardKey.underscore,
|
||||||
|
isShiftPressed: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testWidgets('__AppFlowy__ to bold AppFlowy', ((widgetTester) async {
|
||||||
|
const text = '__AppFlowy_';
|
||||||
|
final editor = widgetTester.editor..insertTextNode('');
|
||||||
|
|
||||||
|
await editor.startTesting();
|
||||||
|
await editor.updateSelection(Selection.single(path: [0], startOffset: 0));
|
||||||
|
final textNode = editor.nodeAtPath([0]) as TextNode;
|
||||||
|
for (var i = 0; i < text.length; i++) {
|
||||||
|
await editor.insertText(textNode, text[i], i);
|
||||||
|
}
|
||||||
|
|
||||||
|
await insertUnderscore(editor);
|
||||||
|
|
||||||
|
final allBold = textNode.allSatisfyBoldInSelection(Selection.single(
|
||||||
|
path: [0], startOffset: 0, endOffset: textNode.toPlainText().length));
|
||||||
|
|
||||||
|
expect(allBold, true);
|
||||||
|
expect(textNode.toPlainText(), 'AppFlowy');
|
||||||
|
}));
|
||||||
|
|
||||||
|
testWidgets('App__Flowy__ to bold AppFlowy', ((widgetTester) async {
|
||||||
|
const text = 'App__Flowy_';
|
||||||
|
final editor = widgetTester.editor..insertTextNode('');
|
||||||
|
|
||||||
|
await editor.startTesting();
|
||||||
|
await editor.updateSelection(Selection.single(path: [0], startOffset: 0));
|
||||||
|
final textNode = editor.nodeAtPath([0]) as TextNode;
|
||||||
|
for (var i = 0; i < text.length; i++) {
|
||||||
|
await editor.insertText(textNode, text[i], i);
|
||||||
|
}
|
||||||
|
|
||||||
|
await insertUnderscore(editor);
|
||||||
|
|
||||||
|
final allBold = textNode.allSatisfyBoldInSelection(Selection.single(
|
||||||
|
path: [0], startOffset: 3, endOffset: textNode.toPlainText().length));
|
||||||
|
|
||||||
|
expect(allBold, true);
|
||||||
|
expect(textNode.toPlainText(), 'AppFlowy');
|
||||||
|
}));
|
||||||
|
|
||||||
|
testWidgets('__*AppFlowy__ to bold *AppFlowy', ((widgetTester) async {
|
||||||
|
const text = '__*AppFlowy_';
|
||||||
|
final editor = widgetTester.editor..insertTextNode('');
|
||||||
|
|
||||||
|
await editor.startTesting();
|
||||||
|
await editor.updateSelection(Selection.single(path: [0], startOffset: 0));
|
||||||
|
final textNode = editor.nodeAtPath([0]) as TextNode;
|
||||||
|
for (var i = 0; i < text.length; i++) {
|
||||||
|
await editor.insertText(textNode, text[i], i);
|
||||||
|
}
|
||||||
|
|
||||||
|
await insertUnderscore(editor);
|
||||||
|
|
||||||
|
final allBold = textNode.allSatisfyBoldInSelection(Selection.single(
|
||||||
|
path: [0], startOffset: 1, endOffset: textNode.toPlainText().length));
|
||||||
|
|
||||||
|
expect(allBold, true);
|
||||||
|
expect(textNode.toPlainText(), '*AppFlowy');
|
||||||
|
}));
|
||||||
|
|
||||||
|
testWidgets('____ nothing changes', ((widgetTester) async {
|
||||||
|
const text = '___';
|
||||||
|
final editor = widgetTester.editor..insertTextNode('');
|
||||||
|
|
||||||
|
await editor.startTesting();
|
||||||
|
await editor.updateSelection(Selection.single(path: [0], startOffset: 0));
|
||||||
|
final textNode = editor.nodeAtPath([0]) as TextNode;
|
||||||
|
for (var i = 0; i < text.length; i++) {
|
||||||
|
await editor.insertText(textNode, text[i], i);
|
||||||
|
}
|
||||||
|
|
||||||
|
await insertUnderscore(editor);
|
||||||
|
|
||||||
|
final allBold = textNode.allSatisfyBoldInSelection(Selection.single(
|
||||||
|
path: [0], startOffset: 0, endOffset: textNode.toPlainText().length));
|
||||||
|
|
||||||
|
expect(allBold, false);
|
||||||
|
expect(textNode.toPlainText(), text);
|
||||||
|
}));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ void main() async {
|
|||||||
);
|
);
|
||||||
|
|
||||||
for (final item in defaultSelectionMenuItems) {
|
for (final item in defaultSelectionMenuItems) {
|
||||||
expect(find.text(item.name()), findsOneWidget);
|
expect(find.text(item.name), findsOneWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
await editor.updateSelection(Selection.single(path: [1], startOffset: 0));
|
await editor.updateSelection(Selection.single(path: [1], startOffset: 0));
|
||||||
|
@ -79,7 +79,7 @@ ShortcutEventHandler _pasteHandler = (editorState, event) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
SelectionMenuItem codeBlockMenuItem = SelectionMenuItem(
|
SelectionMenuItem codeBlockMenuItem = SelectionMenuItem(
|
||||||
name: () => 'Code Block',
|
name: 'Code Block',
|
||||||
icon: (editorState, onSelected) => Icon(
|
icon: (editorState, onSelected) => Icon(
|
||||||
Icons.abc,
|
Icons.abc,
|
||||||
color: onSelected
|
color: onSelected
|
||||||
|
@ -34,7 +34,7 @@ ShortcutEventHandler _insertDividerHandler = (editorState, event) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
SelectionMenuItem dividerMenuItem = SelectionMenuItem(
|
SelectionMenuItem dividerMenuItem = SelectionMenuItem(
|
||||||
name: () => 'Divider',
|
name: 'Divider',
|
||||||
icon: (editorState, onSelected) => Icon(
|
icon: (editorState, onSelected) => Icon(
|
||||||
Icons.horizontal_rule,
|
Icons.horizontal_rule,
|
||||||
color: onSelected
|
color: onSelected
|
||||||
|
@ -5,7 +5,7 @@ import 'package:flutter/services.dart';
|
|||||||
import 'emoji_picker.dart';
|
import 'emoji_picker.dart';
|
||||||
|
|
||||||
SelectionMenuItem emojiMenuItem = SelectionMenuItem(
|
SelectionMenuItem emojiMenuItem = SelectionMenuItem(
|
||||||
name: () => 'Emoji',
|
name: 'Emoji',
|
||||||
icon: (editorState, onSelected) => Icon(
|
icon: (editorState, onSelected) => Icon(
|
||||||
Icons.emoji_emotions_outlined,
|
Icons.emoji_emotions_outlined,
|
||||||
color: onSelected
|
color: onSelected
|
||||||
|
@ -7,7 +7,7 @@ const String kMathEquationType = 'math_equation';
|
|||||||
const String kMathEquationAttr = 'math_equation';
|
const String kMathEquationAttr = 'math_equation';
|
||||||
|
|
||||||
SelectionMenuItem mathEquationMenuItem = SelectionMenuItem(
|
SelectionMenuItem mathEquationMenuItem = SelectionMenuItem(
|
||||||
name: () => 'Math Equation',
|
name: 'Math Equation',
|
||||||
icon: (editorState, onSelected) => Icon(
|
icon: (editorState, onSelected) => Icon(
|
||||||
Icons.text_fields_rounded,
|
Icons.text_fields_rounded,
|
||||||
color: onSelected
|
color: onSelected
|
||||||
|
Reference in New Issue
Block a user