mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: implement command + K to trigger link menu
This commit is contained in:
@ -21,12 +21,21 @@ class LinkMenu extends StatefulWidget {
|
|||||||
|
|
||||||
class _LinkMenuState extends State<LinkMenu> {
|
class _LinkMenuState extends State<LinkMenu> {
|
||||||
final _textEditingController = TextEditingController();
|
final _textEditingController = TextEditingController();
|
||||||
|
final _focusNode = FocusNode();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
_textEditingController.text = widget.linkText ?? '';
|
_textEditingController.text = widget.linkText ?? '';
|
||||||
|
_focusNode.requestFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_focusNode.dispose();
|
||||||
|
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -88,7 +97,7 @@ class _LinkMenuState extends State<LinkMenu> {
|
|||||||
|
|
||||||
Widget _buildInput() {
|
Widget _buildInput() {
|
||||||
return TextField(
|
return TextField(
|
||||||
autofocus: true,
|
focusNode: _focusNode,
|
||||||
style: const TextStyle(fontSize: 14.0),
|
style: const TextStyle(fontSize: 14.0),
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
controller: _textEditingController,
|
controller: _textEditingController,
|
||||||
@ -112,11 +121,7 @@ class _LinkMenuState extends State<LinkMenu> {
|
|||||||
required VoidCallback onPressed,
|
required VoidCallback onPressed,
|
||||||
}) {
|
}) {
|
||||||
return TextButton.icon(
|
return TextButton.icon(
|
||||||
icon: FlowySvg(
|
icon: FlowySvg(name: iconName),
|
||||||
name: iconName,
|
|
||||||
width: 20.0,
|
|
||||||
height: 20.0,
|
|
||||||
),
|
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
minimumSize: const Size.fromHeight(40),
|
minimumSize: const Size.fromHeight(40),
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
|
@ -13,12 +13,14 @@ typedef ToolbarShowValidator = bool Function(EditorState editorState);
|
|||||||
|
|
||||||
class ToolbarItem {
|
class ToolbarItem {
|
||||||
ToolbarItem({
|
ToolbarItem({
|
||||||
|
required this.id,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
this.tooltipsMessage = '',
|
this.tooltipsMessage = '',
|
||||||
required this.validator,
|
required this.validator,
|
||||||
required this.handler,
|
required this.handler,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final String id;
|
||||||
final Widget icon;
|
final Widget icon;
|
||||||
final String tooltipsMessage;
|
final String tooltipsMessage;
|
||||||
final ToolbarShowValidator validator;
|
final ToolbarShowValidator validator;
|
||||||
@ -26,6 +28,7 @@ class ToolbarItem {
|
|||||||
|
|
||||||
factory ToolbarItem.divider() {
|
factory ToolbarItem.divider() {
|
||||||
return ToolbarItem(
|
return ToolbarItem(
|
||||||
|
id: 'divider',
|
||||||
icon: const FlowySvg(name: 'toolbar/divider'),
|
icon: const FlowySvg(name: 'toolbar/divider'),
|
||||||
validator: (editorState) => true,
|
validator: (editorState) => true,
|
||||||
handler: (editorState, context) {},
|
handler: (editorState, context) {},
|
||||||
@ -35,18 +38,21 @@ class ToolbarItem {
|
|||||||
|
|
||||||
List<ToolbarItem> defaultToolbarItems = [
|
List<ToolbarItem> defaultToolbarItems = [
|
||||||
ToolbarItem(
|
ToolbarItem(
|
||||||
|
id: 'appflowy.toolbar.h1',
|
||||||
tooltipsMessage: 'Heading 1',
|
tooltipsMessage: 'Heading 1',
|
||||||
icon: const FlowySvg(name: 'toolbar/h1'),
|
icon: const FlowySvg(name: 'toolbar/h1'),
|
||||||
validator: _onlyShowInSingleTextSelection,
|
validator: _onlyShowInSingleTextSelection,
|
||||||
handler: (editorState, context) => formatHeading(editorState, StyleKey.h1),
|
handler: (editorState, context) => formatHeading(editorState, StyleKey.h1),
|
||||||
),
|
),
|
||||||
ToolbarItem(
|
ToolbarItem(
|
||||||
|
id: 'appflowy.toolbar.h2',
|
||||||
tooltipsMessage: 'Heading 2',
|
tooltipsMessage: 'Heading 2',
|
||||||
icon: const FlowySvg(name: 'toolbar/h2'),
|
icon: const FlowySvg(name: 'toolbar/h2'),
|
||||||
validator: _onlyShowInSingleTextSelection,
|
validator: _onlyShowInSingleTextSelection,
|
||||||
handler: (editorState, context) => formatHeading(editorState, StyleKey.h2),
|
handler: (editorState, context) => formatHeading(editorState, StyleKey.h2),
|
||||||
),
|
),
|
||||||
ToolbarItem(
|
ToolbarItem(
|
||||||
|
id: 'appflowy.toolbar.h3',
|
||||||
tooltipsMessage: 'Heading 3',
|
tooltipsMessage: 'Heading 3',
|
||||||
icon: const FlowySvg(name: 'toolbar/h3'),
|
icon: const FlowySvg(name: 'toolbar/h3'),
|
||||||
validator: _onlyShowInSingleTextSelection,
|
validator: _onlyShowInSingleTextSelection,
|
||||||
@ -54,24 +60,28 @@ List<ToolbarItem> defaultToolbarItems = [
|
|||||||
),
|
),
|
||||||
ToolbarItem.divider(),
|
ToolbarItem.divider(),
|
||||||
ToolbarItem(
|
ToolbarItem(
|
||||||
|
id: 'appflowy.toolbar.bold',
|
||||||
tooltipsMessage: 'Bold',
|
tooltipsMessage: 'Bold',
|
||||||
icon: const FlowySvg(name: 'toolbar/bold'),
|
icon: const FlowySvg(name: 'toolbar/bold'),
|
||||||
validator: _showInTextSelection,
|
validator: _showInTextSelection,
|
||||||
handler: (editorState, context) => formatBold(editorState),
|
handler: (editorState, context) => formatBold(editorState),
|
||||||
),
|
),
|
||||||
ToolbarItem(
|
ToolbarItem(
|
||||||
|
id: 'appflowy.toolbar.italic',
|
||||||
tooltipsMessage: 'Italic',
|
tooltipsMessage: 'Italic',
|
||||||
icon: const FlowySvg(name: 'toolbar/italic'),
|
icon: const FlowySvg(name: 'toolbar/italic'),
|
||||||
validator: _showInTextSelection,
|
validator: _showInTextSelection,
|
||||||
handler: (editorState, context) => formatItalic(editorState),
|
handler: (editorState, context) => formatItalic(editorState),
|
||||||
),
|
),
|
||||||
ToolbarItem(
|
ToolbarItem(
|
||||||
|
id: 'appflowy.toolbar.underline',
|
||||||
tooltipsMessage: 'Underline',
|
tooltipsMessage: 'Underline',
|
||||||
icon: const FlowySvg(name: 'toolbar/underline'),
|
icon: const FlowySvg(name: 'toolbar/underline'),
|
||||||
validator: _showInTextSelection,
|
validator: _showInTextSelection,
|
||||||
handler: (editorState, context) => formatUnderline(editorState),
|
handler: (editorState, context) => formatUnderline(editorState),
|
||||||
),
|
),
|
||||||
ToolbarItem(
|
ToolbarItem(
|
||||||
|
id: 'appflowy.toolbar.strikethrough',
|
||||||
tooltipsMessage: 'Strikethrough',
|
tooltipsMessage: 'Strikethrough',
|
||||||
icon: const FlowySvg(name: 'toolbar/strikethrough'),
|
icon: const FlowySvg(name: 'toolbar/strikethrough'),
|
||||||
validator: _showInTextSelection,
|
validator: _showInTextSelection,
|
||||||
@ -79,12 +89,14 @@ List<ToolbarItem> defaultToolbarItems = [
|
|||||||
),
|
),
|
||||||
ToolbarItem.divider(),
|
ToolbarItem.divider(),
|
||||||
ToolbarItem(
|
ToolbarItem(
|
||||||
|
id: 'appflowy.toolbar.quote',
|
||||||
tooltipsMessage: 'Quote',
|
tooltipsMessage: 'Quote',
|
||||||
icon: const FlowySvg(name: 'toolbar/quote'),
|
icon: const FlowySvg(name: 'toolbar/quote'),
|
||||||
validator: _onlyShowInSingleTextSelection,
|
validator: _onlyShowInSingleTextSelection,
|
||||||
handler: (editorState, context) => formatQuote(editorState),
|
handler: (editorState, context) => formatQuote(editorState),
|
||||||
),
|
),
|
||||||
ToolbarItem(
|
ToolbarItem(
|
||||||
|
id: 'appflowy.toolbar.bulleted_list',
|
||||||
tooltipsMessage: 'Bulleted list',
|
tooltipsMessage: 'Bulleted list',
|
||||||
icon: const FlowySvg(name: 'toolbar/bulleted_list'),
|
icon: const FlowySvg(name: 'toolbar/bulleted_list'),
|
||||||
validator: _onlyShowInSingleTextSelection,
|
validator: _onlyShowInSingleTextSelection,
|
||||||
@ -92,12 +104,14 @@ List<ToolbarItem> defaultToolbarItems = [
|
|||||||
),
|
),
|
||||||
ToolbarItem.divider(),
|
ToolbarItem.divider(),
|
||||||
ToolbarItem(
|
ToolbarItem(
|
||||||
|
id: 'appflowy.toolbar.link',
|
||||||
tooltipsMessage: 'Link',
|
tooltipsMessage: 'Link',
|
||||||
icon: const FlowySvg(name: 'toolbar/link'),
|
icon: const FlowySvg(name: 'toolbar/link'),
|
||||||
validator: _onlyShowInSingleTextSelection,
|
validator: _onlyShowInSingleTextSelection,
|
||||||
handler: (editorState, context) => _showLinkMenu(editorState, context),
|
handler: (editorState, context) => _showLinkMenu(editorState, context),
|
||||||
),
|
),
|
||||||
ToolbarItem(
|
ToolbarItem(
|
||||||
|
id: 'appflowy.toolbar.highlight',
|
||||||
tooltipsMessage: 'Highlight',
|
tooltipsMessage: 'Highlight',
|
||||||
icon: const FlowySvg(name: 'toolbar/highlight'),
|
icon: const FlowySvg(name: 'toolbar/highlight'),
|
||||||
validator: _showInTextSelection,
|
validator: _showInTextSelection,
|
||||||
@ -152,9 +166,7 @@ void _showLinkMenu(EditorState editorState, BuildContext context) {
|
|||||||
linkText: linkText,
|
linkText: linkText,
|
||||||
onSubmitted: (text) {
|
onSubmitted: (text) {
|
||||||
TransactionBuilder(editorState)
|
TransactionBuilder(editorState)
|
||||||
..formatText(node, index, length, {
|
..formatText(node, index, length, {StyleKey.href: text})
|
||||||
StyleKey.href: text,
|
|
||||||
})
|
|
||||||
..commit();
|
..commit();
|
||||||
_dismissLinkMenu();
|
_dismissLinkMenu();
|
||||||
},
|
},
|
||||||
@ -164,9 +176,7 @@ void _showLinkMenu(EditorState editorState, BuildContext context) {
|
|||||||
},
|
},
|
||||||
onRemoveLink: () {
|
onRemoveLink: () {
|
||||||
TransactionBuilder(editorState)
|
TransactionBuilder(editorState)
|
||||||
..formatText(node, index, length, {
|
..formatText(node, index, length, {StyleKey.href: null})
|
||||||
StyleKey.href: null,
|
|
||||||
})
|
|
||||||
..commit();
|
..commit();
|
||||||
_dismissLinkMenu();
|
_dismissLinkMenu();
|
||||||
},
|
},
|
||||||
@ -177,6 +187,7 @@ void _showLinkMenu(EditorState editorState, BuildContext context) {
|
|||||||
Overlay.of(context)?.insert(_linkMenuOverlay!);
|
Overlay.of(context)?.insert(_linkMenuOverlay!);
|
||||||
|
|
||||||
editorState.service.scrollService?.disable();
|
editorState.service.scrollService?.disable();
|
||||||
|
editorState.service.keyboardService?.disable();
|
||||||
editorState.service.selectionService.currentSelection
|
editorState.service.selectionService.currentSelection
|
||||||
.addListener(_dismissLinkMenu);
|
.addListener(_dismissLinkMenu);
|
||||||
}
|
}
|
||||||
@ -186,6 +197,7 @@ void _dismissLinkMenu() {
|
|||||||
_linkMenuOverlay = null;
|
_linkMenuOverlay = null;
|
||||||
|
|
||||||
_editorState?.service.scrollService?.enable();
|
_editorState?.service.scrollService?.enable();
|
||||||
|
_editorState?.service.keyboardService?.enable();
|
||||||
_editorState?.service.selectionService.currentSelection
|
_editorState?.service.selectionService.currentSelection
|
||||||
.removeListener(_dismissLinkMenu);
|
.removeListener(_dismissLinkMenu);
|
||||||
_editorState = null;
|
_editorState = null;
|
||||||
|
@ -36,6 +36,12 @@ AppFlowyKeyEventHandler updateTextStyleByCommandXHandler =
|
|||||||
event.isShiftPressed) {
|
event.isShiftPressed) {
|
||||||
formatHighlight(editorState);
|
formatHighlight(editorState);
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
|
} else if (event.logicalKey == LogicalKeyboardKey.keyK) {
|
||||||
|
if (editorState.service.toolbarService
|
||||||
|
?.triggerHandler('appflowy.toolbar.link') ==
|
||||||
|
true) {
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return KeyEventResult.ignored;
|
return KeyEventResult.ignored;
|
||||||
|
@ -11,6 +11,9 @@ abstract class FlowyToolbarService {
|
|||||||
|
|
||||||
/// Hide the toolbar widget.
|
/// Hide the toolbar widget.
|
||||||
void hide();
|
void hide();
|
||||||
|
|
||||||
|
/// Trigger the specified handler.
|
||||||
|
bool triggerHandler(String id);
|
||||||
}
|
}
|
||||||
|
|
||||||
class FlowyToolbar extends StatefulWidget {
|
class FlowyToolbar extends StatefulWidget {
|
||||||
@ -55,6 +58,17 @@ class _FlowyToolbarState extends State<FlowyToolbar>
|
|||||||
_toolbarOverlay = null;
|
_toolbarOverlay = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool triggerHandler(String id) {
|
||||||
|
final items = defaultToolbarItems.where((item) => item.id == id);
|
||||||
|
if (items.length != 1) {
|
||||||
|
assert(items.length == 1, 'The toolbar item\'s id must be unique');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
items.first.handler(widget.editorState, context);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
|
Reference in New Issue
Block a user