mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge pull request #835 from AppFlowy-IO/feat/copy-styles
Feat: copy styles
This commit is contained in:
commit
6f2e67f40a
@ -46,6 +46,13 @@ class Selection {
|
|||||||
(start.path <= end.path && !pathEquals(start.path, end.path)) ||
|
(start.path <= end.path && !pathEquals(start.path, end.path)) ||
|
||||||
(isSingle && start.offset < end.offset);
|
(isSingle && start.offset < end.offset);
|
||||||
|
|
||||||
|
Selection normalize() {
|
||||||
|
if (isForward) {
|
||||||
|
return Selection(start: end, end: start);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
Selection get reversed => copyWith(start: end, end: start);
|
Selection get reversed => copyWith(start: end, end: start);
|
||||||
|
|
||||||
Selection collapse({bool atStart = false}) {
|
Selection collapse({bool atStart = false}) {
|
||||||
|
@ -17,7 +17,10 @@ const String tagList = "li";
|
|||||||
const String tagParagraph = "p";
|
const String tagParagraph = "p";
|
||||||
const String tagImage = "img";
|
const String tagImage = "img";
|
||||||
const String tagAnchor = "a";
|
const String tagAnchor = "a";
|
||||||
|
const String tagItalic = "i";
|
||||||
const String tagBold = "b";
|
const String tagBold = "b";
|
||||||
|
const String tagUnderline = "u";
|
||||||
|
const String tagDel = "del";
|
||||||
const String tagStrong = "strong";
|
const String tagStrong = "strong";
|
||||||
const String tagSpan = "span";
|
const String tagSpan = "span";
|
||||||
const String tagCode = "code";
|
const String tagCode = "code";
|
||||||
@ -54,7 +57,10 @@ class HTMLToNodesConverter {
|
|||||||
if (child.localName == tagAnchor ||
|
if (child.localName == tagAnchor ||
|
||||||
child.localName == tagSpan ||
|
child.localName == tagSpan ||
|
||||||
child.localName == tagCode ||
|
child.localName == tagCode ||
|
||||||
child.localName == tagStrong) {
|
child.localName == tagStrong ||
|
||||||
|
child.localName == tagUnderline ||
|
||||||
|
child.localName == tagItalic ||
|
||||||
|
child.localName == tagDel) {
|
||||||
_handleRichTextElement(delta, child);
|
_handleRichTextElement(delta, child);
|
||||||
} else if (child.localName == tagBold) {
|
} else if (child.localName == tagBold) {
|
||||||
// Google docs wraps the the content inside the `<b></b>` tag.
|
// Google docs wraps the the content inside the `<b></b>` tag.
|
||||||
@ -128,7 +134,7 @@ class HTMLToNodesConverter {
|
|||||||
if (tuples.length < 2) {
|
if (tuples.length < 2) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
result[tuples[0]] = tuples[1];
|
result[tuples[0].trim()] = tuples[1].trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -142,11 +148,20 @@ class HTMLToNodesConverter {
|
|||||||
|
|
||||||
final fontWeightStr = cssMap["font-weight"];
|
final fontWeightStr = cssMap["font-weight"];
|
||||||
if (fontWeightStr != null) {
|
if (fontWeightStr != null) {
|
||||||
|
if (fontWeightStr == "bold") {
|
||||||
|
attrs[StyleKey.bold] = true;
|
||||||
|
} else {
|
||||||
int? weight = int.tryParse(fontWeightStr);
|
int? weight = int.tryParse(fontWeightStr);
|
||||||
if (weight != null && weight > 500) {
|
if (weight != null && weight > 500) {
|
||||||
attrs["bold"] = true;
|
attrs[StyleKey.bold] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final textDecorationStr = cssMap["text-decoration"];
|
||||||
|
if (textDecorationStr != null) {
|
||||||
|
_assignTextDecorations(attrs, textDecorationStr);
|
||||||
|
}
|
||||||
|
|
||||||
final backgroundColorStr = cssMap["background-color"];
|
final backgroundColorStr = cssMap["background-color"];
|
||||||
final backgroundColor = _tryParseCssColorString(backgroundColorStr);
|
final backgroundColor = _tryParseCssColorString(backgroundColorStr);
|
||||||
@ -155,9 +170,24 @@ class HTMLToNodesConverter {
|
|||||||
'0x${backgroundColor.value.toRadixString(16)}';
|
'0x${backgroundColor.value.toRadixString(16)}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cssMap["font-style"] == "italic") {
|
||||||
|
attrs[StyleKey.italic] = true;
|
||||||
|
}
|
||||||
|
|
||||||
return attrs.isEmpty ? null : attrs;
|
return attrs.isEmpty ? null : attrs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_assignTextDecorations(Attributes attrs, String decorationStr) {
|
||||||
|
final decorations = decorationStr.split(" ");
|
||||||
|
for (final d in decorations) {
|
||||||
|
if (d == "line-through") {
|
||||||
|
attrs[StyleKey.strikethrough] = true;
|
||||||
|
} else if (d == "underline") {
|
||||||
|
attrs[StyleKey.underline] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Try to parse the `rgba(red, greed, blue, alpha)`
|
/// Try to parse the `rgba(red, greed, blue, alpha)`
|
||||||
/// from the string.
|
/// from the string.
|
||||||
Color? _tryParseCssColorString(String? colorString) {
|
Color? _tryParseCssColorString(String? colorString) {
|
||||||
@ -202,7 +232,13 @@ class HTMLToNodesConverter {
|
|||||||
}
|
}
|
||||||
delta.insert(element.text, attributes);
|
delta.insert(element.text, attributes);
|
||||||
} else if (element.localName == tagStrong || element.localName == tagBold) {
|
} else if (element.localName == tagStrong || element.localName == tagBold) {
|
||||||
delta.insert(element.text, {"bold": true});
|
delta.insert(element.text, {StyleKey.bold: true});
|
||||||
|
} else if (element.localName == tagUnderline) {
|
||||||
|
delta.insert(element.text, {StyleKey.underline: true});
|
||||||
|
} else if (element.localName == tagItalic) {
|
||||||
|
delta.insert(element.text, {StyleKey.italic: true});
|
||||||
|
} else if (element.localName == tagDel) {
|
||||||
|
delta.insert(element.text, {StyleKey.strikethrough: true});
|
||||||
} else {
|
} else {
|
||||||
delta.insert(element.text);
|
delta.insert(element.text);
|
||||||
}
|
}
|
||||||
@ -397,6 +433,18 @@ class NodesToHTMLConverter {
|
|||||||
checked: textNode.attributes["checkbox"] == true);
|
checked: textNode.attributes["checkbox"] == true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _textDecorationsFromAttributes(Attributes attributes) {
|
||||||
|
var textDecoration = <String>[];
|
||||||
|
if (attributes[StyleKey.strikethrough] == true) {
|
||||||
|
textDecoration.add("line-through");
|
||||||
|
}
|
||||||
|
if (attributes[StyleKey.underline] == true) {
|
||||||
|
textDecoration.add("underline");
|
||||||
|
}
|
||||||
|
|
||||||
|
return textDecoration.join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
String _attributesToCssStyle(Map<String, dynamic> attributes) {
|
String _attributesToCssStyle(Map<String, dynamic> attributes) {
|
||||||
final cssMap = <String, String>{};
|
final cssMap = <String, String>{};
|
||||||
if (attributes[StyleKey.backgroundColor] != null) {
|
if (attributes[StyleKey.backgroundColor] != null) {
|
||||||
@ -414,6 +462,15 @@ class NodesToHTMLConverter {
|
|||||||
if (attributes[StyleKey.bold] == true) {
|
if (attributes[StyleKey.bold] == true) {
|
||||||
cssMap["font-weight"] = "bold";
|
cssMap["font-weight"] = "bold";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final textDecoration = _textDecorationsFromAttributes(attributes);
|
||||||
|
if (textDecoration.isNotEmpty) {
|
||||||
|
cssMap["text-decoration"] = textDecoration;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attributes[StyleKey.italic] == true) {
|
||||||
|
cssMap["font-style"] = "italic";
|
||||||
|
}
|
||||||
return _cssMapToCssStyle(cssMap);
|
return _cssMapToCssStyle(cssMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -427,6 +484,22 @@ class NodesToHTMLConverter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert the rich text to HTML
|
||||||
|
///
|
||||||
|
/// Use `<b>` for bold only.
|
||||||
|
/// Use `<i>` for italic only.
|
||||||
|
/// Use `<del>` for strikethrough only.
|
||||||
|
/// Use `<u>` for underline only.
|
||||||
|
///
|
||||||
|
/// If the text has multiple styles, use a `<span>`
|
||||||
|
/// to mix the styles.
|
||||||
|
///
|
||||||
|
/// A CSS style string is used to describe the styles.
|
||||||
|
/// The HTML will be:
|
||||||
|
///
|
||||||
|
/// ```html
|
||||||
|
/// <span style="...">Text</span>
|
||||||
|
/// ```
|
||||||
html.Element _deltaToHtml(Delta delta,
|
html.Element _deltaToHtml(Delta delta,
|
||||||
{String? subType, int? end, bool? checked}) {
|
{String? subType, int? end, bool? checked}) {
|
||||||
if (end != null) {
|
if (end != null) {
|
||||||
@ -454,6 +527,21 @@ class NodesToHTMLConverter {
|
|||||||
final strong = html.Element.tag(tagStrong);
|
final strong = html.Element.tag(tagStrong);
|
||||||
strong.append(html.Text(op.content));
|
strong.append(html.Text(op.content));
|
||||||
childNodes.add(strong);
|
childNodes.add(strong);
|
||||||
|
} else if (attributes.length == 1 &&
|
||||||
|
attributes[StyleKey.underline] == true) {
|
||||||
|
final strong = html.Element.tag(tagUnderline);
|
||||||
|
strong.append(html.Text(op.content));
|
||||||
|
childNodes.add(strong);
|
||||||
|
} else if (attributes.length == 1 &&
|
||||||
|
attributes[StyleKey.italic] == true) {
|
||||||
|
final strong = html.Element.tag(tagItalic);
|
||||||
|
strong.append(html.Text(op.content));
|
||||||
|
childNodes.add(strong);
|
||||||
|
} else if (attributes.length == 1 &&
|
||||||
|
attributes[StyleKey.strikethrough] == true) {
|
||||||
|
final strong = html.Element.tag(tagDel);
|
||||||
|
strong.append(html.Text(op.content));
|
||||||
|
childNodes.add(strong);
|
||||||
} else {
|
} else {
|
||||||
final span = html.Element.tag(tagSpan);
|
final span = html.Element.tag(tagSpan);
|
||||||
final cssString = _attributesToCssStyle(attributes);
|
final cssString = _attributesToCssStyle(attributes);
|
||||||
|
@ -6,10 +6,11 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:rich_clipboard/rich_clipboard.dart';
|
import 'package:rich_clipboard/rich_clipboard.dart';
|
||||||
|
|
||||||
_handleCopy(EditorState editorState) async {
|
_handleCopy(EditorState editorState) async {
|
||||||
final selection = editorState.cursorSelection;
|
var selection = editorState.cursorSelection;
|
||||||
if (selection == null || selection.isCollapsed) {
|
if (selection == null || selection.isCollapsed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
selection = selection.normalize();
|
||||||
if (pathEquals(selection.start.path, selection.end.path)) {
|
if (pathEquals(selection.start.path, selection.end.path)) {
|
||||||
final nodeAtPath = editorState.document.nodeAtPath(selection.end.path)!;
|
final nodeAtPath = editorState.document.nodeAtPath(selection.end.path)!;
|
||||||
if (nodeAtPath.type == "text") {
|
if (nodeAtPath.type == "text") {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user