mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: add link preview block (#4137)
* feat: add link preview block * test: add integration test
This commit is contained in:
parent
d5b9063f78
commit
3f9b598493
@ -3,6 +3,7 @@ import 'dart:io';
|
|||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:integration_test/integration_test.dart';
|
import 'package:integration_test/integration_test.dart';
|
||||||
@ -276,6 +277,22 @@ void main() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'auto convert url to link preview block',
|
||||||
|
(widgetTester) async {
|
||||||
|
const url = 'https://appflowy.io';
|
||||||
|
await widgetTester.pasteContent(
|
||||||
|
plainText: url,
|
||||||
|
(editorState) {
|
||||||
|
expect(editorState.document.root.children.length, 2);
|
||||||
|
final node = editorState.getNodeAtPath([0])!;
|
||||||
|
expect(node.type, LinkPreviewBlockKeys.type);
|
||||||
|
expect(node.attributes[LinkPreviewBlockKeys.url], url);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
extension on WidgetTester {
|
extension on WidgetTester {
|
||||||
|
@ -4,6 +4,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/image/cust
|
|||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart' hide TextDirection;
|
import 'package:easy_localization/easy_localization.dart' hide TextDirection;
|
||||||
import 'package:flowy_infra/theme_extension.dart';
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -186,6 +187,28 @@ Map<String, BlockComponentBuilder> getEditorBuilderMap({
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
LinkPreviewBlockKeys.type: LinkPreviewBlockComponentBuilder(
|
||||||
|
configuration: configuration.copyWith(
|
||||||
|
padding: (_) => const EdgeInsets.symmetric(vertical: 10),
|
||||||
|
),
|
||||||
|
cache: LinkPreviewDataCache(),
|
||||||
|
showMenu: true,
|
||||||
|
menuBuilder: (context, node, state) => Positioned(
|
||||||
|
top: 10,
|
||||||
|
right: 0,
|
||||||
|
child: LinkPreviewMenu(
|
||||||
|
node: node,
|
||||||
|
state: state,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
builder: (context, node, url, title, description, imageUrl) =>
|
||||||
|
CustomLinkPreviewWidget(
|
||||||
|
url: url,
|
||||||
|
title: title,
|
||||||
|
description: description,
|
||||||
|
imageUrl: imageUrl,
|
||||||
|
),
|
||||||
|
),
|
||||||
errorBlockComponentBuilderKey: ErrorBlockComponentBuilder(
|
errorBlockComponentBuilderKey: ErrorBlockComponentBuilder(
|
||||||
configuration: configuration,
|
configuration: configuration,
|
||||||
),
|
),
|
||||||
|
@ -6,7 +6,9 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_p
|
|||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_plain_text.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_plain_text.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:string_validator/string_validator.dart';
|
||||||
|
|
||||||
/// Paste.
|
/// Paste.
|
||||||
///
|
///
|
||||||
@ -37,6 +39,12 @@ CommandShortcutEventHandler _pasteCommandHandler = (editorState) {
|
|||||||
final plainText = data.plainText;
|
final plainText = data.plainText;
|
||||||
final image = data.image;
|
final image = data.image;
|
||||||
|
|
||||||
|
// paste as link preview
|
||||||
|
final result = await _pasteAsLinkPreview(editorState, plainText);
|
||||||
|
if (result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Order:
|
// Order:
|
||||||
// 1. in app json format
|
// 1. in app json format
|
||||||
// 2. html
|
// 2. html
|
||||||
@ -75,3 +83,35 @@ CommandShortcutEventHandler _pasteCommandHandler = (editorState) {
|
|||||||
|
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Future<bool> _pasteAsLinkPreview(
|
||||||
|
EditorState editorState,
|
||||||
|
String? text,
|
||||||
|
) async {
|
||||||
|
if (text == null || !isURL(text)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final selection = editorState.selection;
|
||||||
|
if (selection == null ||
|
||||||
|
!selection.isCollapsed ||
|
||||||
|
selection.startIndex != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final node = editorState.getNodeAtPath(selection.start.path);
|
||||||
|
if (node == null ||
|
||||||
|
node.type != ParagraphBlockKeys.type ||
|
||||||
|
node.delta?.toPlainText().isNotEmpty == true) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final transaction = editorState.transaction;
|
||||||
|
transaction.insertNode(
|
||||||
|
selection.start.path,
|
||||||
|
linkPreviewNode(url: text),
|
||||||
|
);
|
||||||
|
await editorState.apply(transaction);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
@ -0,0 +1,91 @@
|
|||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
|
class CustomLinkPreviewWidget extends StatelessWidget {
|
||||||
|
const CustomLinkPreviewWidget({
|
||||||
|
super.key,
|
||||||
|
required this.url,
|
||||||
|
this.title,
|
||||||
|
this.description,
|
||||||
|
this.imageUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String? title;
|
||||||
|
final String? description;
|
||||||
|
final String? imageUrl;
|
||||||
|
final String url;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: () => launchUrlString(url),
|
||||||
|
child: Container(
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
6.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (imageUrl != null)
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(6.0),
|
||||||
|
bottomLeft: Radius.circular(6.0),
|
||||||
|
),
|
||||||
|
child: CachedNetworkImage(
|
||||||
|
imageUrl: imageUrl!,
|
||||||
|
width: 180,
|
||||||
|
height: 120,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
if (title != null)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 4.0),
|
||||||
|
child: FlowyText.medium(
|
||||||
|
title!,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
fontSize: 16.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (description != null)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 4.0),
|
||||||
|
child: FlowyText(
|
||||||
|
description!,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FlowyText(
|
||||||
|
url.toString(),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 2,
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:appflowy/core/config/kv.dart';
|
||||||
|
import 'package:appflowy/startup/startup.dart';
|
||||||
|
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
|
||||||
|
|
||||||
|
class LinkPreviewDataCache implements LinkPreviewDataCacheInterface {
|
||||||
|
@override
|
||||||
|
Future<LinkPreviewData?> get(String url) async {
|
||||||
|
final option =
|
||||||
|
await getIt<KeyValueStorage>().getWithFormat<LinkPreviewData?>(
|
||||||
|
url,
|
||||||
|
(value) => LinkPreviewData.fromJson(jsonDecode(value)),
|
||||||
|
);
|
||||||
|
return option.fold(() => null, (a) => a);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> set(String url, LinkPreviewData data) async {
|
||||||
|
await getIt<KeyValueStorage>().set(
|
||||||
|
url,
|
||||||
|
jsonEncode(data.toJson()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,131 @@
|
|||||||
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class LinkPreviewMenu extends StatefulWidget {
|
||||||
|
const LinkPreviewMenu({
|
||||||
|
super.key,
|
||||||
|
required this.node,
|
||||||
|
required this.state,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Node node;
|
||||||
|
final LinkPreviewBlockComponentState state;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<LinkPreviewMenu> createState() => _LinkPreviewMenuState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LinkPreviewMenuState extends State<LinkPreviewMenu> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
return Container(
|
||||||
|
height: 32,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.cardColor,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
blurRadius: 5,
|
||||||
|
spreadRadius: 1,
|
||||||
|
color: Colors.black.withOpacity(0.1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
borderRadius: BorderRadius.circular(4.0),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const HSpace(4),
|
||||||
|
_CopyLinkButton(
|
||||||
|
onTap: copyImageLink,
|
||||||
|
),
|
||||||
|
const _Divider(),
|
||||||
|
_DeleteButton(
|
||||||
|
onTap: deleteLinkPreviewNode,
|
||||||
|
),
|
||||||
|
const HSpace(4),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void copyImageLink() {
|
||||||
|
final url = widget.node.attributes[ImageBlockKeys.url];
|
||||||
|
if (url != null) {
|
||||||
|
Clipboard.setData(ClipboardData(text: url));
|
||||||
|
showSnackBarMessage(
|
||||||
|
context,
|
||||||
|
LocaleKeys.document_plugins_urlPreview_copiedToPasteBoard.tr(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteLinkPreviewNode() async {
|
||||||
|
final node = widget.node;
|
||||||
|
final editorState = context.read<EditorState>();
|
||||||
|
final transaction = editorState.transaction;
|
||||||
|
transaction.deleteNode(node);
|
||||||
|
transaction.afterSelection = null;
|
||||||
|
await editorState.apply(transaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CopyLinkButton extends StatelessWidget {
|
||||||
|
const _CopyLinkButton({
|
||||||
|
required this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
child: const FlowySvg(
|
||||||
|
FlowySvgs.copy_s,
|
||||||
|
size: Size.square(16),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DeleteButton extends StatelessWidget {
|
||||||
|
const _DeleteButton({
|
||||||
|
required this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
child: const FlowySvg(
|
||||||
|
FlowySvgs.delete_s,
|
||||||
|
size: Size.square(16),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Divider extends StatelessWidget {
|
||||||
|
const _Divider();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: Container(
|
||||||
|
width: 1,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -24,6 +24,9 @@ export 'image/image_selection_menu.dart';
|
|||||||
export 'image/mobile_image_toolbar_item.dart';
|
export 'image/mobile_image_toolbar_item.dart';
|
||||||
export 'inline_math_equation/inline_math_equation.dart';
|
export 'inline_math_equation/inline_math_equation.dart';
|
||||||
export 'inline_math_equation/inline_math_equation_toolbar_item.dart';
|
export 'inline_math_equation/inline_math_equation_toolbar_item.dart';
|
||||||
|
export 'link_preview/custom_link_preview.dart';
|
||||||
|
export 'link_preview/link_preview_cache.dart';
|
||||||
|
export 'link_preview/link_preview_menu.dart';
|
||||||
export 'math_equation/math_equation_block_component.dart';
|
export 'math_equation/math_equation_block_component.dart';
|
||||||
export 'math_equation/mobile_math_equation_toolbar_item.dart';
|
export 'math_equation/mobile_math_equation_toolbar_item.dart';
|
||||||
export 'mobile_toolbar_item/mobile_add_block_toolbar_item.dart';
|
export 'mobile_toolbar_item/mobile_add_block_toolbar_item.dart';
|
||||||
|
@ -59,6 +59,15 @@ packages:
|
|||||||
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
||||||
source: git
|
source: git
|
||||||
version: "2.1.0"
|
version: "2.1.0"
|
||||||
|
appflowy_editor_plugins:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "."
|
||||||
|
ref: "6c49fea"
|
||||||
|
resolved-ref: "6c49feabd65d87ab6ab5c6942df3e85fa5add98d"
|
||||||
|
url: "https://github.com/LucasXu0/appflowy_editor_plugins"
|
||||||
|
source: git
|
||||||
|
version: "0.0.1"
|
||||||
appflowy_popover:
|
appflowy_popover:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -563,6 +572,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.3.1"
|
version: "3.3.1"
|
||||||
|
flutter_chat_types:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_chat_types
|
||||||
|
sha256: e285b588f6d19d907feb1f6d912deaf22e223656769c34093b64e1c59b094fb9
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.6.2"
|
||||||
flutter_colorpicker:
|
flutter_colorpicker:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -585,6 +602,22 @@ packages:
|
|||||||
url: "https://github.com/LucasXu0/emoji_mart.git"
|
url: "https://github.com/LucasXu0/emoji_mart.git"
|
||||||
source: git
|
source: git
|
||||||
version: "1.0.2"
|
version: "1.0.2"
|
||||||
|
flutter_link_previewer:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_link_previewer
|
||||||
|
sha256: "007069e60f42419fb59872beb7a3cc3ea21e9f1bdff5d40239f376fa62ca9f20"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.2"
|
||||||
|
flutter_linkify:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_linkify
|
||||||
|
sha256: "74669e06a8f358fee4512b4320c0b80e51cffc496607931de68d28f099254073"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.0"
|
||||||
flutter_lints:
|
flutter_lints:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@ -994,6 +1027,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.0"
|
version: "0.2.0"
|
||||||
|
linkify:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: linkify
|
||||||
|
sha256: "4139ea77f4651ab9c315b577da2dd108d9aa0bd84b5d03d33323f1970c645832"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.0.0"
|
||||||
lint:
|
lint:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1892,10 +1933,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: url_launcher
|
name: url_launcher
|
||||||
sha256: b1c9e98774adf8820c96fbc7ae3601231d324a7d5ebd8babe27b6dfac91357ba
|
sha256: e9aa5ea75c84cf46b3db4eea212523591211c3cf2e13099ee4ec147f54201c86
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.2.1"
|
version: "6.2.2"
|
||||||
url_launcher_android:
|
url_launcher_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -45,9 +45,10 @@ dependencies:
|
|||||||
url: https://github.com/AppFlowy-IO/appflowy-board.git
|
url: https://github.com/AppFlowy-IO/appflowy-board.git
|
||||||
ref: 93a1b70
|
ref: 93a1b70
|
||||||
appflowy_editor:
|
appflowy_editor:
|
||||||
|
appflowy_editor_plugins:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/AppFlowy-IO/appflowy-editor.git
|
url: https://github.com/LucasXu0/appflowy_editor_plugins
|
||||||
ref: "e6b1336"
|
ref: "6c49fea"
|
||||||
|
|
||||||
appflowy_popover:
|
appflowy_popover:
|
||||||
path: packages/appflowy_popover
|
path: packages/appflowy_popover
|
||||||
@ -156,11 +157,17 @@ dependency_overrides:
|
|||||||
git:
|
git:
|
||||||
url: https://github.com/LucasXu0/app_links
|
url: https://github.com/LucasXu0/app_links
|
||||||
ref: c64ce17
|
ref: c64ce17
|
||||||
|
|
||||||
url_protocol:
|
url_protocol:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/LucasXu0/flutter_url_protocol.git
|
url: https://github.com/LucasXu0/flutter_url_protocol.git
|
||||||
commit: 77a8420
|
commit: 77a8420
|
||||||
|
|
||||||
|
appflowy_editor:
|
||||||
|
git:
|
||||||
|
url: https://github.com/AppFlowy-IO/appflowy-editor.git
|
||||||
|
ref: "e6b1336"
|
||||||
|
|
||||||
# The "flutter_lints" package below contains a set of recommended lints to
|
# The "flutter_lints" package below contains a set of recommended lints to
|
||||||
# encourage good coding practices. The lint set provided by the package is
|
# encourage good coding practices. The lint set provided by the package is
|
||||||
# activated in the `analysis_options.yaml` file located at the root of your
|
# activated in the `analysis_options.yaml` file located at the root of your
|
||||||
|
@ -726,6 +726,9 @@
|
|||||||
"copiedToPasteBoard": "The image link has been copied to the clipboard",
|
"copiedToPasteBoard": "The image link has been copied to the clipboard",
|
||||||
"addAnImage": "Add an image"
|
"addAnImage": "Add an image"
|
||||||
},
|
},
|
||||||
|
"urlPreview": {
|
||||||
|
"copiedToPasteBoard": "The link has been copied to the clipboard"
|
||||||
|
},
|
||||||
"outline": {
|
"outline": {
|
||||||
"addHeadingToCreateOutline": "Add headings to create a table of contents."
|
"addHeadingToCreateOutline": "Add headings to create a table of contents."
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user