QuillControllerConfigurations and clipboard paste

pull/1843/head
Douglas Ward 12 months ago
parent 8854bb0237
commit 03a9d149b3
  1. 1
      lib/src/models/config/quill_configurations.dart
  2. 8
      lib/src/models/config/quill_controller_configurations.dart
  3. 44
      lib/src/widgets/quill/quill_controller.dart
  4. 37
      lib/src/widgets/raw_editor/raw_editor_state.dart
  5. 25
      lib/src/widgets/toolbar/buttons/clipboard_button.dart

@ -1,3 +1,4 @@
export 'editor/editor_configurations.dart'; export 'editor/editor_configurations.dart';
export 'quill_controller_configurations.dart';
export 'quill_shared_configurations.dart'; export 'quill_shared_configurations.dart';
export 'toolbar/simple_toolbar_configurations.dart'; export 'toolbar/simple_toolbar_configurations.dart';

@ -0,0 +1,8 @@
class QuillControllerConfigurations {
const QuillControllerConfigurations({this.onClipboardPaste});
/// Callback when the user pastes and data has not already been processed
///
/// Return true if the paste operation was handled
final Future<bool> Function()? onClipboardPaste;
}

@ -18,6 +18,7 @@ class QuillController extends ChangeNotifier {
QuillController({ QuillController({
required Document document, required Document document,
required TextSelection selection, required TextSelection selection,
this.configurations = const QuillControllerConfigurations(),
this.keepStyleOnNewLine = true, this.keepStyleOnNewLine = true,
this.onReplaceText, this.onReplaceText,
this.onDelete, this.onDelete,
@ -27,13 +28,18 @@ class QuillController extends ChangeNotifier {
}) : _document = document, }) : _document = document,
_selection = selection; _selection = selection;
factory QuillController.basic() { factory QuillController.basic(
{QuillControllerConfigurations configurations =
const QuillControllerConfigurations()}) {
return QuillController( return QuillController(
configurations: configurations,
document: Document(), document: Document(),
selection: const TextSelection.collapsed(offset: 0), selection: const TextSelection.collapsed(offset: 0),
); );
} }
final QuillControllerConfigurations configurations;
/// Document managed by this controller. /// Document managed by this controller.
Document _document; Document _document;
@ -505,34 +511,10 @@ class QuillController extends ChangeNotifier {
return false; return false;
} }
/// Returns whether paste was handled here. /// Returns whether paste operation was handled here.
/// updateEditor is called if paste operation was successful.
Future<bool> clipboardPaste({void Function()? updateEditor}) async { Future<bool> clipboardPaste({void Function()? updateEditor}) async {
if (readOnly) return true; if (readOnly || !selection.isValid) return true;
// When image copied internally in the editor
if (_copiedImageUrl != null) {
final index = selection.baseOffset;
final length = selection.extentOffset - index;
replaceText(
index,
length,
BlockEmbed.image(_copiedImageUrl!.url),
null,
);
if (_copiedImageUrl!.styleString.isNotEmpty) {
formatText(
getEmbedNode(this, index + 1).offset,
1,
StyleAttribute(_copiedImageUrl!.styleString),
);
}
copiedImageUrl = null;
return true;
}
if (!selection.isValid) {
return true;
}
if (await _pasteHTML()) { if (await _pasteHTML()) {
updateEditor?.call(); updateEditor?.call();
@ -553,6 +535,12 @@ class QuillController extends ChangeNotifier {
updateEditor?.call(); updateEditor?.call();
return true; return true;
} }
if (await configurations.onClipboardPaste?.call() == true) {
updateEditor?.call();
return true;
}
return false; return false;
} }

@ -11,9 +11,10 @@ import 'package:flutter/scheduler.dart' show SchedulerBinding;
import 'package:flutter/services.dart' import 'package:flutter/services.dart'
show show
Clipboard, Clipboard,
ClipboardData,
HardwareKeyboard, HardwareKeyboard,
LogicalKeyboardKey,
KeyDownEvent, KeyDownEvent,
LogicalKeyboardKey,
SystemChannels, SystemChannels,
TextInputControl; TextInputControl;
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart' import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'
@ -31,6 +32,7 @@ import '../../models/structs/offset_value.dart';
import '../../models/structs/vertical_spacing.dart'; import '../../models/structs/vertical_spacing.dart';
import '../../utils/cast.dart'; import '../../utils/cast.dart';
import '../../utils/delta.dart'; import '../../utils/delta.dart';
import '../../utils/embeds.dart';
import '../../utils/platform.dart'; import '../../utils/platform.dart';
import '../editor/editor.dart'; import '../editor/editor.dart';
import '../others/cursor.dart'; import '../others/cursor.dart';
@ -148,8 +150,37 @@ class QuillRawEditorState extends EditorState
/// Paste text from [Clipboard]. /// Paste text from [Clipboard].
@override @override
Future<void> pasteText(SelectionChangedCause cause) async { Future<void> pasteText(SelectionChangedCause cause) async {
if (await controller.clipboardPaste( if (controller.readOnly) {
updateEditor: () => bringIntoView(textEditingValue.selection.extent))) { return;
}
// When image copied internally in the editor
final copiedImageUrl = controller.copiedImageUrl;
if (copiedImageUrl != null) {
final index = textEditingValue.selection.baseOffset;
final length = textEditingValue.selection.extentOffset - index;
controller.replaceText(
index,
length,
BlockEmbed.image(copiedImageUrl.url),
null,
);
if (copiedImageUrl.styleString.isNotEmpty) {
controller.formatText(
getEmbedNode(controller, index + 1).offset,
1,
StyleAttribute(copiedImageUrl.styleString),
);
}
controller.copiedImageUrl = null;
await Clipboard.setData(
const ClipboardData(text: ''),
);
return;
}
if (await controller.clipboardPaste()) {
bringIntoView(textEditingValue.selection.extent);
return; return;
} }

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:super_clipboard/super_clipboard.dart';
import '../../../../extensions.dart'; import '../../../../extensions.dart';
import '../../../../flutter_quill.dart'; import '../../../../flutter_quill.dart';
@ -11,19 +12,28 @@ import '../base_button/base_value_button.dart';
enum ClipboardAction { cut, copy, paste } enum ClipboardAction { cut, copy, paste }
class ClipboardMonitor { class ClipboardMonitor {
final ClipboardStatusNotifier _clipboardStatus = ClipboardStatusNotifier(); bool _canPaste = false;
bool get canPaste => _canPaste;
Timer? _timer; Timer? _timer;
void monitorClipboard(bool add, void Function() listener) { void monitorClipboard(bool add, void Function() listener) {
if (kIsWeb) return; if (kIsWeb) return;
if (add) { if (add) {
_clipboardStatus.addListener(listener);
_timer = Timer.periodic( _timer = Timer.periodic(
const Duration(seconds: 1), (timer) => _clipboardStatus.update()); const Duration(seconds: 1), (timer) => _update(listener));
} else { } else {
_timer?.cancel(); _timer?.cancel();
_clipboardStatus.removeListener(listener); }
}
Future<void> _update(void Function() listener) async {
final reader = await SystemClipboard.instance?.read();
if (reader != null) {
final available = reader.platformFormats;
if (_canPaste != available.isNotEmpty) {
_canPaste = available.isNotEmpty;
listener();
}
} }
} }
} }
@ -54,10 +64,7 @@ class QuillToolbarClipboardButtonState
case ClipboardAction.copy: case ClipboardAction.copy:
return !controller.selection.isCollapsed; return !controller.selection.isCollapsed;
case ClipboardAction.paste: case ClipboardAction.paste:
return !controller.readOnly && return !controller.readOnly && (kIsWeb || widget._monitor.canPaste);
(kIsWeb ||
widget._monitor._clipboardStatus.value ==
ClipboardStatus.pasteable);
} }
} }

Loading…
Cancel
Save