feat: callout (#1732)
* feat: add callout plugin * refactor: add SelectionMenuItem.node factory makes calloutMenuItem more readable * feat: add color picker * feat: add popover to callout * feat: add emoji to callout * fix: store tint name * fix: remove leading underscores * fix: revert export of editor_entry * refactor: move color tint names to appflowy_editor * fix: #1732 only re-insert text node if it's parent is text node too while deleting * docs: doc comment for SelectionMenuItem.node * fix: disable callout plugin should be re-enabled after #1753 is done * fix: typo --------- Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
This commit is contained in:
parent
a0483322ef
commit
2c28123f59
6 changed files with 321 additions and 28 deletions
|
|
@ -73,5 +73,23 @@
|
|||
"backgroundColorPink": "Pink background",
|
||||
"@backgroundColorPink": {},
|
||||
"backgroundColorRed": "Red background",
|
||||
"@backgroundColorRed": {}
|
||||
"@backgroundColorRed": {},
|
||||
"tint1": "Tint 1",
|
||||
"tint2": "Tint 2",
|
||||
"tint3": "Tint 3",
|
||||
"tint4": "Tint 4",
|
||||
"tint5": "Tint 5",
|
||||
"tint6": "Tint 6",
|
||||
"tint7": "Tint 7",
|
||||
"tint8": "Tint 8",
|
||||
"tint9": "Tint 9",
|
||||
"lightLightTint1": "Purple",
|
||||
"lightLightTint2": "Pink",
|
||||
"lightLightTint3": "Light Pink",
|
||||
"lightLightTint4": "Orange",
|
||||
"lightLightTint5": "Yellow",
|
||||
"lightLightTint6": "Lime",
|
||||
"lightLightTint7": "Green",
|
||||
"lightLightTint8": "Aqua",
|
||||
"lightLightTint9": "Blue"
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
import 'package:intl/src/intl_helpers.dart';
|
||||
|
|
@ -40,28 +41,28 @@ import 'messages_zh-TW.dart' as messages_zh_tw;
|
|||
|
||||
typedef Future<dynamic> LibraryLoader();
|
||||
Map<String, LibraryLoader> _deferredLibraries = {
|
||||
'bn_BN': () => new Future.value(null),
|
||||
'ca': () => new Future.value(null),
|
||||
'cs_CZ': () => new Future.value(null),
|
||||
'de_DE': () => new Future.value(null),
|
||||
'en': () => new Future.value(null),
|
||||
'es_VE': () => new Future.value(null),
|
||||
'fr_CA': () => new Future.value(null),
|
||||
'fr_FR': () => new Future.value(null),
|
||||
'hi_IN': () => new Future.value(null),
|
||||
'hu_HU': () => new Future.value(null),
|
||||
'id_ID': () => new Future.value(null),
|
||||
'it_IT': () => new Future.value(null),
|
||||
'ja_JP': () => new Future.value(null),
|
||||
'ml_IN': () => new Future.value(null),
|
||||
'nl_NL': () => new Future.value(null),
|
||||
'pl_PL': () => new Future.value(null),
|
||||
'pt_BR': () => new Future.value(null),
|
||||
'pt_PT': () => new Future.value(null),
|
||||
'ru_RU': () => new Future.value(null),
|
||||
'tr_TR': () => new Future.value(null),
|
||||
'zh_CN': () => new Future.value(null),
|
||||
'zh_TW': () => new Future.value(null),
|
||||
'bn_BN': () => new SynchronousFuture(null),
|
||||
'ca': () => new SynchronousFuture(null),
|
||||
'cs_CZ': () => new SynchronousFuture(null),
|
||||
'de_DE': () => new SynchronousFuture(null),
|
||||
'en': () => new SynchronousFuture(null),
|
||||
'es_VE': () => new SynchronousFuture(null),
|
||||
'fr_CA': () => new SynchronousFuture(null),
|
||||
'fr_FR': () => new SynchronousFuture(null),
|
||||
'hi_IN': () => new SynchronousFuture(null),
|
||||
'hu_HU': () => new SynchronousFuture(null),
|
||||
'id_ID': () => new SynchronousFuture(null),
|
||||
'it_IT': () => new SynchronousFuture(null),
|
||||
'ja_JP': () => new SynchronousFuture(null),
|
||||
'ml_IN': () => new SynchronousFuture(null),
|
||||
'nl_NL': () => new SynchronousFuture(null),
|
||||
'pl_PL': () => new SynchronousFuture(null),
|
||||
'pt_BR': () => new SynchronousFuture(null),
|
||||
'pt_PT': () => new SynchronousFuture(null),
|
||||
'ru_RU': () => new SynchronousFuture(null),
|
||||
'tr_TR': () => new SynchronousFuture(null),
|
||||
'zh_CN': () => new SynchronousFuture(null),
|
||||
'zh_TW': () => new SynchronousFuture(null),
|
||||
};
|
||||
|
||||
MessageLookupByLibrary? _findExact(String localeName) {
|
||||
|
|
@ -116,18 +117,18 @@ MessageLookupByLibrary? _findExact(String localeName) {
|
|||
}
|
||||
|
||||
/// User programs should call this before using [localeName] for messages.
|
||||
Future<bool> initializeMessages(String localeName) async {
|
||||
Future<bool> initializeMessages(String localeName) {
|
||||
var availableLocale = Intl.verifiedLocale(
|
||||
localeName, (locale) => _deferredLibraries[locale] != null,
|
||||
onFailure: (_) => null);
|
||||
if (availableLocale == null) {
|
||||
return new Future.value(false);
|
||||
return new SynchronousFuture(false);
|
||||
}
|
||||
var lib = _deferredLibraries[availableLocale];
|
||||
await (lib == null ? new Future.value(false) : lib());
|
||||
lib == null ? new SynchronousFuture(false) : lib();
|
||||
initializeInternalMessageLookup(() => new CompositeMessageLookup());
|
||||
messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor);
|
||||
return new Future.value(true);
|
||||
return new SynchronousFuture(true);
|
||||
}
|
||||
|
||||
bool _messagesExistFor(String locale) {
|
||||
|
|
|
|||
|
|
@ -63,11 +63,29 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
"highlight": MessageLookupByLibrary.simpleMessage("Highlight"),
|
||||
"image": MessageLookupByLibrary.simpleMessage("Image"),
|
||||
"italic": MessageLookupByLibrary.simpleMessage("Italic"),
|
||||
"lightLightTint1": MessageLookupByLibrary.simpleMessage("Purple"),
|
||||
"lightLightTint2": MessageLookupByLibrary.simpleMessage("Pink"),
|
||||
"lightLightTint3": MessageLookupByLibrary.simpleMessage("Light Pink"),
|
||||
"lightLightTint4": MessageLookupByLibrary.simpleMessage("Orange"),
|
||||
"lightLightTint5": MessageLookupByLibrary.simpleMessage("Yellow"),
|
||||
"lightLightTint6": MessageLookupByLibrary.simpleMessage("Lime"),
|
||||
"lightLightTint7": MessageLookupByLibrary.simpleMessage("Green"),
|
||||
"lightLightTint8": MessageLookupByLibrary.simpleMessage("Aqua"),
|
||||
"lightLightTint9": MessageLookupByLibrary.simpleMessage("Blue"),
|
||||
"link": MessageLookupByLibrary.simpleMessage("Link"),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage("Numbered List"),
|
||||
"quote": MessageLookupByLibrary.simpleMessage("Quote"),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage("Strikethrough"),
|
||||
"text": MessageLookupByLibrary.simpleMessage("Text"),
|
||||
"tint1": MessageLookupByLibrary.simpleMessage("Tint 1"),
|
||||
"tint2": MessageLookupByLibrary.simpleMessage("Tint 2"),
|
||||
"tint3": MessageLookupByLibrary.simpleMessage("Tint 3"),
|
||||
"tint4": MessageLookupByLibrary.simpleMessage("Tint 4"),
|
||||
"tint5": MessageLookupByLibrary.simpleMessage("Tint 5"),
|
||||
"tint6": MessageLookupByLibrary.simpleMessage("Tint 6"),
|
||||
"tint7": MessageLookupByLibrary.simpleMessage("Tint 7"),
|
||||
"tint8": MessageLookupByLibrary.simpleMessage("Tint 8"),
|
||||
"tint9": MessageLookupByLibrary.simpleMessage("Tint 9"),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("Underline")
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -420,6 +420,186 @@ class AppFlowyEditorLocalizations {
|
|||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Tint 1`
|
||||
String get tint1 {
|
||||
return Intl.message(
|
||||
'Tint 1',
|
||||
name: 'tint1',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Tint 2`
|
||||
String get tint2 {
|
||||
return Intl.message(
|
||||
'Tint 2',
|
||||
name: 'tint2',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Tint 3`
|
||||
String get tint3 {
|
||||
return Intl.message(
|
||||
'Tint 3',
|
||||
name: 'tint3',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Tint 4`
|
||||
String get tint4 {
|
||||
return Intl.message(
|
||||
'Tint 4',
|
||||
name: 'tint4',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Tint 5`
|
||||
String get tint5 {
|
||||
return Intl.message(
|
||||
'Tint 5',
|
||||
name: 'tint5',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Tint 6`
|
||||
String get tint6 {
|
||||
return Intl.message(
|
||||
'Tint 6',
|
||||
name: 'tint6',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Tint 7`
|
||||
String get tint7 {
|
||||
return Intl.message(
|
||||
'Tint 7',
|
||||
name: 'tint7',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Tint 8`
|
||||
String get tint8 {
|
||||
return Intl.message(
|
||||
'Tint 8',
|
||||
name: 'tint8',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Tint 9`
|
||||
String get tint9 {
|
||||
return Intl.message(
|
||||
'Tint 9',
|
||||
name: 'tint9',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Purple`
|
||||
String get lightLightTint1 {
|
||||
return Intl.message(
|
||||
'Purple',
|
||||
name: 'lightLightTint1',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Pink`
|
||||
String get lightLightTint2 {
|
||||
return Intl.message(
|
||||
'Pink',
|
||||
name: 'lightLightTint2',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Light Pink`
|
||||
String get lightLightTint3 {
|
||||
return Intl.message(
|
||||
'Light Pink',
|
||||
name: 'lightLightTint3',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Orange`
|
||||
String get lightLightTint4 {
|
||||
return Intl.message(
|
||||
'Orange',
|
||||
name: 'lightLightTint4',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Yellow`
|
||||
String get lightLightTint5 {
|
||||
return Intl.message(
|
||||
'Yellow',
|
||||
name: 'lightLightTint5',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Lime`
|
||||
String get lightLightTint6 {
|
||||
return Intl.message(
|
||||
'Lime',
|
||||
name: 'lightLightTint6',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Green`
|
||||
String get lightLightTint7 {
|
||||
return Intl.message(
|
||||
'Green',
|
||||
name: 'lightLightTint7',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Aqua`
|
||||
String get lightLightTint8 {
|
||||
return Intl.message(
|
||||
'Aqua',
|
||||
name: 'lightLightTint8',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Blue`
|
||||
String get lightLightTint9 {
|
||||
return Intl.message(
|
||||
'Blue',
|
||||
name: 'lightLightTint9',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppLocalizationDelegate
|
||||
|
|
|
|||
|
|
@ -53,6 +53,81 @@ class SelectionMenuItem {
|
|||
editorState.apply(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a selection menu entry for inserting a [Node].
|
||||
/// [name] and [iconData] define the appearance within the selection menu.
|
||||
///
|
||||
/// The insert position is determined by the result of [replace] and
|
||||
/// [insertBefore]
|
||||
/// If no values are provided for [replace] and [insertBefore] the node is
|
||||
/// inserted after the current selection.
|
||||
/// [replace] takes precedence over [insertBefore]
|
||||
///
|
||||
/// [updateSelection] can be used to update the selection after the node
|
||||
/// has been inserted.
|
||||
factory SelectionMenuItem.node({
|
||||
required String name,
|
||||
required IconData iconData,
|
||||
required List<String> keywords,
|
||||
required Node Function(EditorState editorState) nodeBuilder,
|
||||
bool Function(EditorState editorState, TextNode textNode)? insertBefore,
|
||||
bool Function(EditorState editorState, TextNode textNode)? replace,
|
||||
Selection? Function(
|
||||
EditorState editorState,
|
||||
Path insertPath,
|
||||
bool replaced,
|
||||
bool insertedBefore,
|
||||
)?
|
||||
updateSelection,
|
||||
}) {
|
||||
return SelectionMenuItem(
|
||||
name: () => name,
|
||||
icon: (editorState, onSelected) => Icon(
|
||||
iconData,
|
||||
color: onSelected
|
||||
? editorState.editorStyle.selectionMenuItemSelectedIconColor
|
||||
: editorState.editorStyle.selectionMenuItemIconColor,
|
||||
size: 18.0,
|
||||
),
|
||||
keywords: keywords,
|
||||
handler: (editorState, _, __) {
|
||||
final selection =
|
||||
editorState.service.selectionService.currentSelection.value;
|
||||
final textNodes = editorState
|
||||
.service.selectionService.currentSelectedNodes
|
||||
.whereType<TextNode>();
|
||||
if (textNodes.length != 1 || selection == null) {
|
||||
return;
|
||||
}
|
||||
final textNode = textNodes.first;
|
||||
final node = nodeBuilder(editorState);
|
||||
final transaction = editorState.transaction;
|
||||
final bReplace = replace?.call(editorState, textNode) ?? false;
|
||||
final bInsertBefore =
|
||||
insertBefore?.call(editorState, textNode) ?? false;
|
||||
|
||||
//default insert after
|
||||
var path = textNode.path.next;
|
||||
if (bReplace) {
|
||||
path = textNode.path;
|
||||
} else if (bInsertBefore) {
|
||||
path = textNode.path;
|
||||
}
|
||||
|
||||
transaction
|
||||
..insertNode(path, node)
|
||||
..afterSelection = updateSelection?.call(
|
||||
editorState, path, bReplace, bInsertBefore) ??
|
||||
selection;
|
||||
|
||||
if (bReplace) {
|
||||
transaction.deleteNode(textNode);
|
||||
}
|
||||
|
||||
editorState.apply(transaction);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SelectionMenuWidget extends StatefulWidget {
|
||||
|
|
|
|||
|
|
@ -120,7 +120,8 @@ KeyEventResult _backDeleteToPreviousTextNode(
|
|||
) {
|
||||
if (textNode.next == null &&
|
||||
textNode.children.isEmpty &&
|
||||
textNode.parent?.parent != null) {
|
||||
textNode.parent?.parent != null &&
|
||||
textNode.parent is TextNode) {
|
||||
transaction
|
||||
..deleteNode(textNode)
|
||||
..insertNode(textNode.parent!.path.next, textNode)
|
||||
|
|
|
|||
Loading…
Reference in a new issue