mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: implement theme customizer
This commit is contained in:
parent
988e8db798
commit
68997a9c93
@ -22,8 +22,8 @@ editor.insertTextNode(text);
|
||||
|
||||
// Insert the same text, but with the heading style.
|
||||
editor.insertTextNode(text, attributes: {
|
||||
StyleKey.subtype: StyleKey.heading,
|
||||
StyleKey.heading: StyleKey.h1,
|
||||
BuiltInAttributeKey.subtype: BuiltInAttributeKey.heading,
|
||||
BuiltInAttributeKey.heading: BuiltInAttributeKey.h1,
|
||||
});
|
||||
|
||||
// Insert our text with the bulleted list style and the bold style.
|
||||
@ -31,10 +31,10 @@ editor.insertTextNode(text, attributes: {
|
||||
editor.insertTextNode(
|
||||
'',
|
||||
attributes: {
|
||||
StyleKey.subtype: StyleKey.bulletedList,
|
||||
BuiltInAttributeKey.subtype: BuiltInAttributeKey.bulletedList,
|
||||
},
|
||||
delta: Delta([
|
||||
TextInsert(text, {StyleKey.bold: true}),
|
||||
TextInsert(text, {BuiltInAttributeKey.bold: true}),
|
||||
]),
|
||||
);
|
||||
```
|
||||
|
@ -21,6 +21,10 @@ class MyApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
localizationsDelegates: const [
|
||||
AppFlowyEditorLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: AppFlowyEditorLocalizations.delegate.supportedLocales,
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.blue,
|
||||
@ -40,7 +44,9 @@ class MyHomePage extends StatefulWidget {
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
int _pageIndex = 0;
|
||||
late EditorState _editorState;
|
||||
EditorState? _editorState;
|
||||
bool darkMode = false;
|
||||
EditorStyle _editorStyle = EditorStyle.defaultStyle();
|
||||
Future<String>? _jsonString;
|
||||
|
||||
@override
|
||||
@ -78,24 +84,29 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
return FutureBuilder<String>(
|
||||
future: jsonString,
|
||||
builder: (_, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
_editorState = EditorState(
|
||||
if (snapshot.hasData &&
|
||||
snapshot.connectionState == ConnectionState.done) {
|
||||
_editorState ??= EditorState(
|
||||
document: StateTree.fromJson(
|
||||
Map<String, Object>.from(
|
||||
json.decode(snapshot.data!),
|
||||
),
|
||||
),
|
||||
);
|
||||
_editorState.logConfiguration
|
||||
_editorState!.logConfiguration
|
||||
..level = LogLevel.all
|
||||
..handler = (message) {
|
||||
debugPrint(message);
|
||||
};
|
||||
return SizedBox(
|
||||
_editorState!.operationStream.listen((event) {
|
||||
debugPrint('Operation: ${event.toJson()}');
|
||||
});
|
||||
return Container(
|
||||
color: darkMode ? Colors.black : Colors.white,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: AppFlowyEditor(
|
||||
editorState: _editorState,
|
||||
editorStyle: const EditorStyle.defaultStyle(),
|
||||
editorState: _editorState!,
|
||||
editorStyle: _editorStyle,
|
||||
shortcutEvents: [
|
||||
underscoreToItalicEvent,
|
||||
],
|
||||
@ -127,12 +138,26 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
onPressed: () => _switchToPage(2),
|
||||
),
|
||||
ActionButton(
|
||||
icon: const Icon(Icons.print),
|
||||
onPressed: () => {_exportDocument(_editorState)}),
|
||||
icon: const Icon(Icons.print),
|
||||
onPressed: () => _exportDocument(_editorState!),
|
||||
),
|
||||
ActionButton(
|
||||
icon: const Icon(Icons.import_export),
|
||||
onPressed: () => _importDocument(),
|
||||
),
|
||||
ActionButton(
|
||||
icon: const Icon(Icons.color_lens),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_editorStyle = _editorStyle.copyWith(
|
||||
textStyle: darkMode
|
||||
? BuiltInTextStyle.builtIn()
|
||||
: BuiltInTextStyle.builtInDarkMode(),
|
||||
);
|
||||
darkMode = !darkMode;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -166,6 +191,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
void _switchToPage(int pageIndex) {
|
||||
if (pageIndex != _pageIndex) {
|
||||
setState(() {
|
||||
_editorState = null;
|
||||
_pageIndex = pageIndex;
|
||||
});
|
||||
}
|
||||
|
@ -26,3 +26,5 @@ export 'src/service/input_service.dart';
|
||||
export 'src/service/shortcut_event/keybinding.dart';
|
||||
export 'src/service/shortcut_event/shortcut_event.dart';
|
||||
export 'src/service/shortcut_event/shortcut_event_handler.dart';
|
||||
export 'src/extensions/attributes_extension.dart';
|
||||
export 'src/l10n/l10n.dart';
|
||||
|
@ -0,0 +1,35 @@
|
||||
{
|
||||
"@@locale": "ca",
|
||||
"bold": "",
|
||||
"@bold": {},
|
||||
"bulletedList": "",
|
||||
"@bulletedList": {},
|
||||
"checkbox": "",
|
||||
"@checkbox": {},
|
||||
"embedCode": "",
|
||||
"@embedCode": {},
|
||||
"heading1": "",
|
||||
"@heading1": {},
|
||||
"heading2": "",
|
||||
"@heading2": {},
|
||||
"heading3": "",
|
||||
"@heading3": {},
|
||||
"highlight": "",
|
||||
"@highlight": {},
|
||||
"image": "",
|
||||
"@image": {},
|
||||
"italic": "",
|
||||
"@italic": {},
|
||||
"link": "",
|
||||
"@link": {},
|
||||
"numberedList": "",
|
||||
"@numberedList": {},
|
||||
"quote": "",
|
||||
"@quote": {},
|
||||
"strikethrough": "",
|
||||
"@strikethrough": {},
|
||||
"text": "",
|
||||
"@text": {},
|
||||
"underline": "",
|
||||
"@underline": {}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
{
|
||||
"@@locale": "de-DE",
|
||||
"bold": "",
|
||||
"@bold": {},
|
||||
"bulletedList": "",
|
||||
"@bulletedList": {},
|
||||
"checkbox": "",
|
||||
"@checkbox": {},
|
||||
"embedCode": "",
|
||||
"@embedCode": {},
|
||||
"heading1": "",
|
||||
"@heading1": {},
|
||||
"heading2": "",
|
||||
"@heading2": {},
|
||||
"heading3": "",
|
||||
"@heading3": {},
|
||||
"highlight": "",
|
||||
"@highlight": {},
|
||||
"image": "",
|
||||
"@image": {},
|
||||
"italic": "",
|
||||
"@italic": {},
|
||||
"link": "",
|
||||
"@link": {},
|
||||
"numberedList": "",
|
||||
"@numberedList": {},
|
||||
"quote": "",
|
||||
"@quote": {},
|
||||
"strikethrough": "",
|
||||
"@strikethrough": {},
|
||||
"text": "",
|
||||
"@text": {},
|
||||
"underline": "",
|
||||
"@underline": {}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
{
|
||||
"@@locale": "en",
|
||||
"bold": "Bold",
|
||||
"@bold": {},
|
||||
"bulletedList": "Bulleted List",
|
||||
"@bulletedList": {},
|
||||
"checkbox": "Checkbox",
|
||||
"@checkbox": {},
|
||||
"embedCode": "Embed Code",
|
||||
"@embedCode": {},
|
||||
"heading1": "H1",
|
||||
"@heading1": {},
|
||||
"heading2": "H2",
|
||||
"@heading2": {},
|
||||
"heading3": "H3",
|
||||
"@heading3": {},
|
||||
"highlight": "Highlight",
|
||||
"@highlight": {},
|
||||
"image": "Image",
|
||||
"@image": {},
|
||||
"italic": "Italic",
|
||||
"@italic": {},
|
||||
"link": "Link",
|
||||
"@link": {},
|
||||
"numberedList": "Numbered List",
|
||||
"@numberedList": {},
|
||||
"quote": "Quote",
|
||||
"@quote": {},
|
||||
"strikethrough": "Strikethrough",
|
||||
"@strikethrough": {},
|
||||
"text": "Text",
|
||||
"@text": {},
|
||||
"underline": "Underline",
|
||||
"@underline": {}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
{
|
||||
"@@locale": "es-VE",
|
||||
"bold": "",
|
||||
"@bold": {},
|
||||
"bulletedList": "",
|
||||
"@bulletedList": {},
|
||||
"checkbox": "",
|
||||
"@checkbox": {},
|
||||
"embedCode": "",
|
||||
"@embedCode": {},
|
||||
"heading1": "",
|
||||
"@heading1": {},
|
||||
"heading2": "",
|
||||
"@heading2": {},
|
||||
"heading3": "",
|
||||
"@heading3": {},
|
||||
"highlight": "",
|
||||
"@highlight": {},
|
||||
"image": "",
|
||||
"@image": {},
|
||||
"italic": "",
|
||||
"@italic": {},
|
||||
"link": "",
|
||||
"@link": {},
|
||||
"numberedList": "",
|
||||
"@numberedList": {},
|
||||
"quote": "",
|
||||
"@quote": {},
|
||||
"strikethrough": "",
|
||||
"@strikethrough": {},
|
||||
"text": "",
|
||||
"@text": {},
|
||||
"underline": "",
|
||||
"@underline": {}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
{
|
||||
"@@locale": "fr-CA",
|
||||
"bold": "",
|
||||
"@bold": {},
|
||||
"bulletedList": "",
|
||||
"@bulletedList": {},
|
||||
"checkbox": "",
|
||||
"@checkbox": {},
|
||||
"embedCode": "",
|
||||
"@embedCode": {},
|
||||
"heading1": "",
|
||||
"@heading1": {},
|
||||
"heading2": "",
|
||||
"@heading2": {},
|
||||
"heading3": "",
|
||||
"@heading3": {},
|
||||
"highlight": "",
|
||||
"@highlight": {},
|
||||
"image": "",
|
||||
"@image": {},
|
||||
"italic": "",
|
||||
"@italic": {},
|
||||
"link": "",
|
||||
"@link": {},
|
||||
"numberedList": "",
|
||||
"@numberedList": {},
|
||||
"quote": "",
|
||||
"@quote": {},
|
||||
"strikethrough": "",
|
||||
"@strikethrough": {},
|
||||
"text": "",
|
||||
"@text": {},
|
||||
"underline": "",
|
||||
"@underline": {}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
{
|
||||
"@@locale": "fr-FR",
|
||||
"bold": "",
|
||||
"@bold": {},
|
||||
"bulletedList": "",
|
||||
"@bulletedList": {},
|
||||
"checkbox": "",
|
||||
"@checkbox": {},
|
||||
"embedCode": "",
|
||||
"@embedCode": {},
|
||||
"heading1": "",
|
||||
"@heading1": {},
|
||||
"heading2": "",
|
||||
"@heading2": {},
|
||||
"heading3": "",
|
||||
"@heading3": {},
|
||||
"highlight": "",
|
||||
"@highlight": {},
|
||||
"image": "",
|
||||
"@image": {},
|
||||
"italic": "",
|
||||
"@italic": {},
|
||||
"link": "",
|
||||
"@link": {},
|
||||
"numberedList": "",
|
||||
"@numberedList": {},
|
||||
"quote": "",
|
||||
"@quote": {},
|
||||
"strikethrough": "",
|
||||
"@strikethrough": {},
|
||||
"text": "",
|
||||
"@text": {},
|
||||
"underline": "",
|
||||
"@underline": {}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
{
|
||||
"@@locale": "hu-HU",
|
||||
"bold": "",
|
||||
"@bold": {},
|
||||
"bulletedList": "",
|
||||
"@bulletedList": {},
|
||||
"checkbox": "",
|
||||
"@checkbox": {},
|
||||
"embedCode": "",
|
||||
"@embedCode": {},
|
||||
"heading1": "",
|
||||
"@heading1": {},
|
||||
"heading2": "",
|
||||
"@heading2": {},
|
||||
"heading3": "",
|
||||
"@heading3": {},
|
||||
"highlight": "",
|
||||
"@highlight": {},
|
||||
"image": "",
|
||||
"@image": {},
|
||||
"italic": "",
|
||||
"@italic": {},
|
||||
"link": "",
|
||||
"@link": {},
|
||||
"numberedList": "",
|
||||
"@numberedList": {},
|
||||
"quote": "",
|
||||
"@quote": {},
|
||||
"strikethrough": "",
|
||||
"@strikethrough": {},
|
||||
"text": "",
|
||||
"@text": {},
|
||||
"underline": "",
|
||||
"@underline": {}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
{
|
||||
"@@locale": "id-ID",
|
||||
"bold": "",
|
||||
"@bold": {},
|
||||
"bulletedList": "",
|
||||
"@bulletedList": {},
|
||||
"checkbox": "",
|
||||
"@checkbox": {},
|
||||
"embedCode": "",
|
||||
"@embedCode": {},
|
||||
"heading1": "",
|
||||
"@heading1": {},
|
||||
"heading2": "",
|
||||
"@heading2": {},
|
||||
"heading3": "",
|
||||
"@heading3": {},
|
||||
"highlight": "",
|
||||
"@highlight": {},
|
||||
"image": "",
|
||||
"@image": {},
|
||||
"italic": "",
|
||||
"@italic": {},
|
||||
"link": "",
|
||||
"@link": {},
|
||||
"numberedList": "",
|
||||
"@numberedList": {},
|
||||
"quote": "",
|
||||
"@quote": {},
|
||||
"strikethrough": "",
|
||||
"@strikethrough": {},
|
||||
"text": "",
|
||||
"@text": {},
|
||||
"underline": "",
|
||||
"@underline": {}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
{
|
||||
"@@locale": "it-IT",
|
||||
"bold": "",
|
||||
"@bold": {},
|
||||
"bulletedList": "",
|
||||
"@bulletedList": {},
|
||||
"checkbox": "",
|
||||
"@checkbox": {},
|
||||
"embedCode": "",
|
||||
"@embedCode": {},
|
||||
"heading1": "",
|
||||
"@heading1": {},
|
||||
"heading2": "",
|
||||
"@heading2": {},
|
||||
"heading3": "",
|
||||
"@heading3": {},
|
||||
"highlight": "",
|
||||
"@highlight": {},
|
||||
"image": "",
|
||||
"@image": {},
|
||||
"italic": "",
|
||||
"@italic": {},
|
||||
"link": "",
|
||||
"@link": {},
|
||||
"numberedList": "",
|
||||
"@numberedList": {},
|
||||
"quote": "",
|
||||
"@quote": {},
|
||||
"strikethrough": "",
|
||||
"@strikethrough": {},
|
||||
"text": "",
|
||||
"@text": {},
|
||||
"underline": "",
|
||||
"@underline": {}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
{
|
||||
"@@locale": "ja-JP",
|
||||
"bold": "",
|
||||
"@bold": {},
|
||||
"bulletedList": "",
|
||||
"@bulletedList": {},
|
||||
"checkbox": "",
|
||||
"@checkbox": {},
|
||||
"embedCode": "",
|
||||
"@embedCode": {},
|
||||
"heading1": "",
|
||||
"@heading1": {},
|
||||
"heading2": "",
|
||||
"@heading2": {},
|
||||
"heading3": "",
|
||||
"@heading3": {},
|
||||
"highlight": "",
|
||||
"@highlight": {},
|
||||
"image": "",
|
||||
"@image": {},
|
||||
"italic": "",
|
||||
"@italic": {},
|
||||
"link": "",
|
||||
"@link": {},
|
||||
"numberedList": "",
|
||||
"@numberedList": {},
|
||||
"quote": "",
|
||||
"@quote": {},
|
||||
"strikethrough": "",
|
||||
"@strikethrough": {},
|
||||
"text": "",
|
||||
"@text": {},
|
||||
"underline": "",
|
||||
"@underline": {}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
{
|
||||
"@@locale": "pl-PL",
|
||||
"bold": "",
|
||||
"@bold": {},
|
||||
"bulletedList": "",
|
||||
"@bulletedList": {},
|
||||
"checkbox": "",
|
||||
"@checkbox": {},
|
||||
"embedCode": "",
|
||||
"@embedCode": {},
|
||||
"heading1": "",
|
||||
"@heading1": {},
|
||||
"heading2": "",
|
||||
"@heading2": {},
|
||||
"heading3": "",
|
||||
"@heading3": {},
|
||||
"highlight": "",
|
||||
"@highlight": {},
|
||||
"image": "",
|
||||
"@image": {},
|
||||
"italic": "",
|
||||
"@italic": {},
|
||||
"link": "",
|
||||
"@link": {},
|
||||
"numberedList": "",
|
||||
"@numberedList": {},
|
||||
"quote": "",
|
||||
"@quote": {},
|
||||
"strikethrough": "",
|
||||
"@strikethrough": {},
|
||||
"text": "",
|
||||
"@text": {},
|
||||
"underline": "",
|
||||
"@underline": {}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
{
|
||||
"@@locale": "pt-BR",
|
||||
"bold": "",
|
||||
"@bold": {},
|
||||
"bulletedList": "",
|
||||
"@bulletedList": {},
|
||||
"checkbox": "",
|
||||
"@checkbox": {},
|
||||
"embedCode": "",
|
||||
"@embedCode": {},
|
||||
"heading1": "",
|
||||
"@heading1": {},
|
||||
"heading2": "",
|
||||
"@heading2": {},
|
||||
"heading3": "",
|
||||
"@heading3": {},
|
||||
"highlight": "",
|
||||
"@highlight": {},
|
||||
"image": "",
|
||||
"@image": {},
|
||||
"italic": "",
|
||||
"@italic": {},
|
||||
"link": "",
|
||||
"@link": {},
|
||||
"numberedList": "",
|
||||
"@numberedList": {},
|
||||
"quote": "",
|
||||
"@quote": {},
|
||||
"strikethrough": "",
|
||||
"@strikethrough": {},
|
||||
"text": "",
|
||||
"@text": {},
|
||||
"underline": "",
|
||||
"@underline": {}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
{
|
||||
"@@locale": "pt-PT",
|
||||
"bold": "",
|
||||
"@bold": {},
|
||||
"bulletedList": "",
|
||||
"@bulletedList": {},
|
||||
"checkbox": "",
|
||||
"@checkbox": {},
|
||||
"embedCode": "",
|
||||
"@embedCode": {},
|
||||
"heading1": "",
|
||||
"@heading1": {},
|
||||
"heading2": "",
|
||||
"@heading2": {},
|
||||
"heading3": "",
|
||||
"@heading3": {},
|
||||
"highlight": "",
|
||||
"@highlight": {},
|
||||
"image": "",
|
||||
"@image": {},
|
||||
"italic": "",
|
||||
"@italic": {},
|
||||
"link": "",
|
||||
"@link": {},
|
||||
"numberedList": "",
|
||||
"@numberedList": {},
|
||||
"quote": "",
|
||||
"@quote": {},
|
||||
"strikethrough": "",
|
||||
"@strikethrough": {},
|
||||
"text": "",
|
||||
"@text": {},
|
||||
"underline": "",
|
||||
"@underline": {}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
{
|
||||
"@@locale": "ru-RU",
|
||||
"bold": "",
|
||||
"@bold": {},
|
||||
"bulletedList": "",
|
||||
"@bulletedList": {},
|
||||
"checkbox": "",
|
||||
"@checkbox": {},
|
||||
"embedCode": "",
|
||||
"@embedCode": {},
|
||||
"heading1": "",
|
||||
"@heading1": {},
|
||||
"heading2": "",
|
||||
"@heading2": {},
|
||||
"heading3": "",
|
||||
"@heading3": {},
|
||||
"highlight": "",
|
||||
"@highlight": {},
|
||||
"image": "",
|
||||
"@image": {},
|
||||
"italic": "",
|
||||
"@italic": {},
|
||||
"link": "",
|
||||
"@link": {},
|
||||
"numberedList": "",
|
||||
"@numberedList": {},
|
||||
"quote": "",
|
||||
"@quote": {},
|
||||
"strikethrough": "",
|
||||
"@strikethrough": {},
|
||||
"text": "",
|
||||
"@text": {},
|
||||
"underline": "",
|
||||
"@underline": {}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
{
|
||||
"@@locale": "tr-TR",
|
||||
"bold": "",
|
||||
"@bold": {},
|
||||
"bulletedList": "",
|
||||
"@bulletedList": {},
|
||||
"checkbox": "",
|
||||
"@checkbox": {},
|
||||
"embedCode": "",
|
||||
"@embedCode": {},
|
||||
"heading1": "",
|
||||
"@heading1": {},
|
||||
"heading2": "",
|
||||
"@heading2": {},
|
||||
"heading3": "",
|
||||
"@heading3": {},
|
||||
"highlight": "",
|
||||
"@highlight": {},
|
||||
"image": "",
|
||||
"@image": {},
|
||||
"italic": "",
|
||||
"@italic": {},
|
||||
"link": "",
|
||||
"@link": {},
|
||||
"numberedList": "",
|
||||
"@numberedList": {},
|
||||
"quote": "",
|
||||
"@quote": {},
|
||||
"strikethrough": "",
|
||||
"@strikethrough": {},
|
||||
"text": "",
|
||||
"@text": {},
|
||||
"underline": "",
|
||||
"@underline": {}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
{
|
||||
"@@locale": "zh-CN",
|
||||
"bold": "加粗",
|
||||
"@bold": {},
|
||||
"bulletedList": "无序列表",
|
||||
"@bulletedList": {},
|
||||
"checkbox": "复选框",
|
||||
"@checkbox": {},
|
||||
"embedCode": "内嵌代码",
|
||||
"@embedCode": {},
|
||||
"heading1": "标题 1",
|
||||
"@heading1": {},
|
||||
"heading2": "标题 2",
|
||||
"@heading2": {},
|
||||
"heading3": "标题 3",
|
||||
"@heading3": {},
|
||||
"highlight": "高亮",
|
||||
"@highlight": {},
|
||||
"image": "图片",
|
||||
"@image": {},
|
||||
"italic": "斜体",
|
||||
"@italic": {},
|
||||
"link": "链接",
|
||||
"@link": {},
|
||||
"numberedList": "有序列表",
|
||||
"@numberedList": {},
|
||||
"quote": "引用",
|
||||
"@quote": {},
|
||||
"strikethrough": "删除线",
|
||||
"@strikethrough": {},
|
||||
"text": "文字",
|
||||
"@text": {},
|
||||
"underline": "下划线",
|
||||
"@underline": {}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
{
|
||||
"@@locale": "zh-TW",
|
||||
"bold": "",
|
||||
"@bold": {},
|
||||
"bulletedList": "",
|
||||
"@bulletedList": {},
|
||||
"checkbox": "",
|
||||
"@checkbox": {},
|
||||
"embedCode": "",
|
||||
"@embedCode": {},
|
||||
"heading1": "",
|
||||
"@heading1": {},
|
||||
"heading2": "",
|
||||
"@heading2": {},
|
||||
"heading3": "",
|
||||
"@heading3": {},
|
||||
"highlight": "",
|
||||
"@highlight": {},
|
||||
"image": "",
|
||||
"@image": {},
|
||||
"italic": "",
|
||||
"@italic": {},
|
||||
"link": "",
|
||||
"@link": {},
|
||||
"numberedList": "",
|
||||
"@numberedList": {},
|
||||
"quote": "",
|
||||
"@quote": {},
|
||||
"strikethrough": "",
|
||||
"@strikethrough": {},
|
||||
"text": "",
|
||||
"@text": {},
|
||||
"underline": "",
|
||||
"@underline": {}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
///
|
||||
/// Supported partial rendering types:
|
||||
/// bold, italic,
|
||||
/// underline, strikethrough,
|
||||
/// color, font,
|
||||
/// href
|
||||
///
|
||||
/// Supported global rendering types:
|
||||
/// heading: h1, h2, h3, h4, h5, h6, ...
|
||||
/// block quote,
|
||||
/// list: ordered list, bulleted list,
|
||||
/// code block
|
||||
///
|
||||
class BuiltInAttributeKey {
|
||||
static String bold = 'bold';
|
||||
static String italic = 'italic';
|
||||
static String underline = 'underline';
|
||||
static String strikethrough = 'strikethrough';
|
||||
static String color = 'color';
|
||||
static String backgroundColor = 'backgroundColor';
|
||||
static String font = 'font';
|
||||
static String href = 'href';
|
||||
|
||||
static String subtype = 'subtype';
|
||||
static String heading = 'heading';
|
||||
static String h1 = 'h1';
|
||||
static String h2 = 'h2';
|
||||
static String h3 = 'h3';
|
||||
static String h4 = 'h4';
|
||||
static String h5 = 'h5';
|
||||
static String h6 = 'h6';
|
||||
|
||||
static String bulletedList = 'bulleted-list';
|
||||
static String numberList = 'number-list';
|
||||
|
||||
static String quote = 'quote';
|
||||
static String checkbox = 'checkbox';
|
||||
static String code = 'code';
|
||||
static String number = 'number';
|
||||
|
||||
static List<String> partialStyleKeys = [
|
||||
BuiltInAttributeKey.bold,
|
||||
BuiltInAttributeKey.italic,
|
||||
BuiltInAttributeKey.underline,
|
||||
BuiltInAttributeKey.strikethrough,
|
||||
BuiltInAttributeKey.backgroundColor,
|
||||
BuiltInAttributeKey.href,
|
||||
BuiltInAttributeKey.code,
|
||||
];
|
||||
|
||||
static List<String> globalStyleKeys = [
|
||||
BuiltInAttributeKey.subtype,
|
||||
BuiltInAttributeKey.heading,
|
||||
BuiltInAttributeKey.checkbox,
|
||||
BuiltInAttributeKey.bulletedList,
|
||||
BuiltInAttributeKey.numberList,
|
||||
BuiltInAttributeKey.quote,
|
||||
];
|
||||
}
|
@ -24,6 +24,13 @@ class Node extends ChangeNotifier with LinkedListEntry<Node> {
|
||||
return null;
|
||||
}
|
||||
|
||||
String get id {
|
||||
if (subtype != null) {
|
||||
return '$type/$subtype';
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
Path get path => _path();
|
||||
|
||||
Attributes get attributes => _attributes;
|
||||
|
@ -60,7 +60,11 @@ class EditorState {
|
||||
List<SelectionMenuItem> selectionMenuItems = [];
|
||||
|
||||
/// Stores the editor style.
|
||||
EditorStyle editorStyle = const EditorStyle.defaultStyle();
|
||||
EditorStyle editorStyle = EditorStyle.defaultStyle();
|
||||
|
||||
/// Operation stream.
|
||||
Stream<Operation> get operationStream => _observer.stream;
|
||||
final StreamController<Operation> _observer = StreamController.broadcast();
|
||||
|
||||
final UndoManager undoManager = UndoManager();
|
||||
Selection? _cursorSelection;
|
||||
@ -72,6 +76,15 @@ class EditorState {
|
||||
return _cursorSelection;
|
||||
}
|
||||
|
||||
RenderBox? get renderBox {
|
||||
final renderObject =
|
||||
service.scrollServiceKey.currentContext?.findRenderObject();
|
||||
if (renderObject != null && renderObject is RenderBox) {
|
||||
return renderObject;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
updateCursorSelection(Selection? cursorSelection,
|
||||
[CursorUpdateReason reason = CursorUpdateReason.others]) {
|
||||
// broadcast to other users here
|
||||
@ -151,5 +164,6 @@ class EditorState {
|
||||
} else if (op is TextEditOperation) {
|
||||
document.textEdit(op.path, op.delta);
|
||||
}
|
||||
_observer.add(op);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,93 @@
|
||||
import 'package:appflowy_editor/src/document/attributes.dart';
|
||||
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
extension NodeAttributesExtensions on Attributes {
|
||||
String? get heading {
|
||||
if (containsKey(BuiltInAttributeKey.subtype) &&
|
||||
containsKey(BuiltInAttributeKey.heading) &&
|
||||
this[BuiltInAttributeKey.subtype] == BuiltInAttributeKey.heading &&
|
||||
this[BuiltInAttributeKey.heading] is String) {
|
||||
return this[BuiltInAttributeKey.heading];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
bool get quote {
|
||||
return containsKey(BuiltInAttributeKey.quote);
|
||||
}
|
||||
|
||||
int? get number {
|
||||
if (containsKey(BuiltInAttributeKey.number) &&
|
||||
this[BuiltInAttributeKey.number] is int) {
|
||||
return this[BuiltInAttributeKey.number];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
bool get code {
|
||||
if (containsKey(BuiltInAttributeKey.code) &&
|
||||
this[BuiltInAttributeKey.code] == true) {
|
||||
return this[BuiltInAttributeKey.code];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool get check {
|
||||
if (containsKey(BuiltInAttributeKey.checkbox) &&
|
||||
this[BuiltInAttributeKey.checkbox] is bool) {
|
||||
return this[BuiltInAttributeKey.checkbox];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
extension DeltaAttributesExtensions on Attributes {
|
||||
bool get bold {
|
||||
return (containsKey(BuiltInAttributeKey.bold) &&
|
||||
this[BuiltInAttributeKey.bold] == true);
|
||||
}
|
||||
|
||||
bool get italic {
|
||||
return (containsKey(BuiltInAttributeKey.italic) &&
|
||||
this[BuiltInAttributeKey.italic] == true);
|
||||
}
|
||||
|
||||
bool get underline {
|
||||
return (containsKey(BuiltInAttributeKey.underline) &&
|
||||
this[BuiltInAttributeKey.underline] == true);
|
||||
}
|
||||
|
||||
bool get strikethrough {
|
||||
return (containsKey(BuiltInAttributeKey.strikethrough) &&
|
||||
this[BuiltInAttributeKey.strikethrough] == true);
|
||||
}
|
||||
|
||||
Color? get color {
|
||||
if (containsKey(BuiltInAttributeKey.color) &&
|
||||
this[BuiltInAttributeKey.color] is String) {
|
||||
return Color(
|
||||
int.parse(this[BuiltInAttributeKey.color]),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Color? get backgroundColor {
|
||||
if (containsKey(BuiltInAttributeKey.backgroundColor) &&
|
||||
this[BuiltInAttributeKey.backgroundColor] is String) {
|
||||
return Color(
|
||||
int.parse(this[BuiltInAttributeKey.backgroundColor]),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String? get href {
|
||||
if (containsKey(BuiltInAttributeKey.href) &&
|
||||
this[BuiltInAttributeKey.href] is String) {
|
||||
return this[BuiltInAttributeKey.href];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ import 'package:appflowy_editor/src/document/path.dart';
|
||||
import 'package:appflowy_editor/src/document/position.dart';
|
||||
import 'package:appflowy_editor/src/document/selection.dart';
|
||||
import 'package:appflowy_editor/src/document/text_delta.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
||||
|
||||
extension TextNodeExtension on TextNode {
|
||||
dynamic getAttributeInSelection(Selection selection, String styleKey) {
|
||||
@ -29,27 +29,28 @@ extension TextNodeExtension on TextNode {
|
||||
}
|
||||
|
||||
bool allSatisfyLinkInSelection(Selection selection) =>
|
||||
allSatisfyInSelection(selection, StyleKey.href, (value) {
|
||||
allSatisfyInSelection(selection, BuiltInAttributeKey.href, (value) {
|
||||
return value != null;
|
||||
});
|
||||
|
||||
bool allSatisfyBoldInSelection(Selection selection) =>
|
||||
allSatisfyInSelection(selection, StyleKey.bold, (value) {
|
||||
allSatisfyInSelection(selection, BuiltInAttributeKey.bold, (value) {
|
||||
return value == true;
|
||||
});
|
||||
|
||||
bool allSatisfyItalicInSelection(Selection selection) =>
|
||||
allSatisfyInSelection(selection, StyleKey.italic, (value) {
|
||||
allSatisfyInSelection(selection, BuiltInAttributeKey.italic, (value) {
|
||||
return value == true;
|
||||
});
|
||||
|
||||
bool allSatisfyUnderlineInSelection(Selection selection) =>
|
||||
allSatisfyInSelection(selection, StyleKey.underline, (value) {
|
||||
allSatisfyInSelection(selection, BuiltInAttributeKey.underline, (value) {
|
||||
return value == true;
|
||||
});
|
||||
|
||||
bool allSatisfyStrikethroughInSelection(Selection selection) =>
|
||||
allSatisfyInSelection(selection, StyleKey.strikethrough, (value) {
|
||||
allSatisfyInSelection(selection, BuiltInAttributeKey.strikethrough,
|
||||
(value) {
|
||||
return value == true;
|
||||
});
|
||||
|
||||
@ -58,11 +59,11 @@ extension TextNodeExtension on TextNode {
|
||||
String styleKey,
|
||||
bool Function(dynamic value) test,
|
||||
) {
|
||||
if (StyleKey.globalStyleKeys.contains(styleKey)) {
|
||||
if (BuiltInAttributeKey.globalStyleKeys.contains(styleKey)) {
|
||||
if (attributes.containsKey(styleKey)) {
|
||||
return test(attributes[styleKey]);
|
||||
}
|
||||
} else if (StyleKey.partialStyleKeys.contains(styleKey)) {
|
||||
} else if (BuiltInAttributeKey.partialStyleKeys.contains(styleKey)) {
|
||||
final ops = delta.whereType<TextInsert>();
|
||||
final startOffset =
|
||||
selection.isBackward ? selection.start.offset : selection.end.offset;
|
||||
@ -120,28 +121,28 @@ extension TextNodeExtension on TextNode {
|
||||
extension TextNodesExtension on List<TextNode> {
|
||||
bool allSatisfyBoldInSelection(Selection selection) => allSatisfyInSelection(
|
||||
selection,
|
||||
StyleKey.bold,
|
||||
BuiltInAttributeKey.bold,
|
||||
(value) => value == true,
|
||||
);
|
||||
|
||||
bool allSatisfyItalicInSelection(Selection selection) =>
|
||||
allSatisfyInSelection(
|
||||
selection,
|
||||
StyleKey.italic,
|
||||
BuiltInAttributeKey.italic,
|
||||
(value) => value == true,
|
||||
);
|
||||
|
||||
bool allSatisfyUnderlineInSelection(Selection selection) =>
|
||||
allSatisfyInSelection(
|
||||
selection,
|
||||
StyleKey.underline,
|
||||
BuiltInAttributeKey.underline,
|
||||
(value) => value == true,
|
||||
);
|
||||
|
||||
bool allSatisfyStrikethroughInSelection(Selection selection) =>
|
||||
allSatisfyInSelection(
|
||||
selection,
|
||||
StyleKey.strikethrough,
|
||||
BuiltInAttributeKey.strikethrough,
|
||||
(value) => value == true,
|
||||
);
|
||||
|
||||
|
@ -0,0 +1,73 @@
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
extension TextSpanExtensions on TextSpan {
|
||||
TextSpan copyWith({
|
||||
String? text,
|
||||
TextStyle? style,
|
||||
List<InlineSpan>? children,
|
||||
GestureRecognizer? recognizer,
|
||||
String? semanticsLabel,
|
||||
}) {
|
||||
return TextSpan(
|
||||
text: text ?? this.text,
|
||||
style: style ?? this.style,
|
||||
children: children ?? this.children,
|
||||
recognizer: recognizer ?? this.recognizer,
|
||||
semanticsLabel: semanticsLabel ?? this.semanticsLabel,
|
||||
);
|
||||
}
|
||||
|
||||
TextSpan updateTextStyle(TextStyle? other) {
|
||||
if (other == null) {
|
||||
return this;
|
||||
}
|
||||
return copyWith(
|
||||
style: style?.combine(other),
|
||||
children: children?.map((child) {
|
||||
if (child is TextSpan) {
|
||||
return child.updateTextStyle(other);
|
||||
}
|
||||
return child;
|
||||
}).toList(growable: false),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension TextStyleExtensions on TextStyle {
|
||||
TextStyle combine(TextStyle? other) {
|
||||
if (other == null) {
|
||||
return this;
|
||||
}
|
||||
if (!other.inherit) {
|
||||
return other;
|
||||
}
|
||||
|
||||
return copyWith(
|
||||
color: other.color,
|
||||
backgroundColor: other.backgroundColor,
|
||||
fontSize: other.fontSize,
|
||||
fontWeight: other.fontWeight,
|
||||
fontStyle: other.fontStyle,
|
||||
letterSpacing: other.letterSpacing,
|
||||
wordSpacing: other.wordSpacing,
|
||||
textBaseline: other.textBaseline,
|
||||
height: other.height,
|
||||
leadingDistribution: other.leadingDistribution,
|
||||
locale: other.locale,
|
||||
foreground: other.foreground,
|
||||
background: other.background,
|
||||
shadows: other.shadows,
|
||||
fontFeatures: other.fontFeatures,
|
||||
decoration: TextDecoration.combine([
|
||||
if (decoration != null) decoration!,
|
||||
if (other.decoration != null) other.decoration!,
|
||||
]),
|
||||
decorationColor: other.decorationColor,
|
||||
decorationStyle: other.decorationStyle,
|
||||
decorationThickness: other.decorationThickness,
|
||||
fontFamilyFallback: other.fontFamilyFallback,
|
||||
overflow: other.overflow,
|
||||
);
|
||||
}
|
||||
}
|
@ -4,11 +4,11 @@ import 'dart:ui';
|
||||
import 'package:appflowy_editor/src/document/attributes.dart';
|
||||
import 'package:appflowy_editor/src/document/node.dart';
|
||||
import 'package:appflowy_editor/src/document/text_delta.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||
import 'package:appflowy_editor/src/extensions/color_extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:html/parser.dart' show parse;
|
||||
import 'package:html/dom.dart' as html;
|
||||
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
||||
|
||||
class HTMLTag {
|
||||
static const h1 = "h1";
|
||||
@ -99,7 +99,8 @@ class HTMLToNodesConverter {
|
||||
|
||||
for (final child in element.nodes.toList()) {
|
||||
if (child is html.Element) {
|
||||
result.addAll(_handleElement(child, {"subtype": StyleKey.quote}));
|
||||
result.addAll(
|
||||
_handleElement(child, {"subtype": BuiltInAttributeKey.quote}));
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,11 +175,11 @@ class HTMLToNodesConverter {
|
||||
final fontWeightStr = cssMap["font-weight"];
|
||||
if (fontWeightStr != null) {
|
||||
if (fontWeightStr == "bold") {
|
||||
attrs[StyleKey.bold] = true;
|
||||
attrs[BuiltInAttributeKey.bold] = true;
|
||||
} else {
|
||||
int? weight = int.tryParse(fontWeightStr);
|
||||
if (weight != null && weight > 500) {
|
||||
attrs[StyleKey.bold] = true;
|
||||
attrs[BuiltInAttributeKey.bold] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -193,12 +194,12 @@ class HTMLToNodesConverter {
|
||||
? null
|
||||
: ColorExtension.tryFromRgbaString(backgroundColorStr);
|
||||
if (backgroundColor != null) {
|
||||
attrs[StyleKey.backgroundColor] =
|
||||
attrs[BuiltInAttributeKey.backgroundColor] =
|
||||
'0x${backgroundColor.value.toRadixString(16)}';
|
||||
}
|
||||
|
||||
if (cssMap["font-style"] == "italic") {
|
||||
attrs[StyleKey.italic] = true;
|
||||
attrs[BuiltInAttributeKey.italic] = true;
|
||||
}
|
||||
|
||||
return attrs.isEmpty ? null : attrs;
|
||||
@ -208,9 +209,9 @@ class HTMLToNodesConverter {
|
||||
final decorations = decorationStr.split(" ");
|
||||
for (final d in decorations) {
|
||||
if (d == "line-through") {
|
||||
attrs[StyleKey.strikethrough] = true;
|
||||
attrs[BuiltInAttributeKey.strikethrough] = true;
|
||||
} else if (d == "underline") {
|
||||
attrs[StyleKey.underline] = true;
|
||||
attrs[BuiltInAttributeKey.underline] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -228,13 +229,13 @@ class HTMLToNodesConverter {
|
||||
delta.insert(element.text, attributes);
|
||||
} else if (element.localName == HTMLTag.strong ||
|
||||
element.localName == HTMLTag.bold) {
|
||||
delta.insert(element.text, {StyleKey.bold: true});
|
||||
delta.insert(element.text, {BuiltInAttributeKey.bold: true});
|
||||
} else if (element.localName == HTMLTag.underline) {
|
||||
delta.insert(element.text, {StyleKey.underline: true});
|
||||
delta.insert(element.text, {BuiltInAttributeKey.underline: true});
|
||||
} else if (element.localName == HTMLTag.italic) {
|
||||
delta.insert(element.text, {StyleKey.italic: true});
|
||||
delta.insert(element.text, {BuiltInAttributeKey.italic: true});
|
||||
} else if (element.localName == HTMLTag.del) {
|
||||
delta.insert(element.text, {StyleKey.strikethrough: true});
|
||||
delta.insert(element.text, {BuiltInAttributeKey.strikethrough: true});
|
||||
} else {
|
||||
delta.insert(element.text);
|
||||
}
|
||||
@ -273,7 +274,7 @@ class HTMLToNodesConverter {
|
||||
final textNode =
|
||||
TextNode(type: "text", delta: delta, attributes: attributes);
|
||||
if (isCheckbox) {
|
||||
textNode.attributes["subtype"] = StyleKey.checkbox;
|
||||
textNode.attributes["subtype"] = BuiltInAttributeKey.checkbox;
|
||||
textNode.attributes["checkbox"] = checked;
|
||||
}
|
||||
return textNode;
|
||||
@ -291,8 +292,8 @@ class HTMLToNodesConverter {
|
||||
List<Node> _handleUnorderedList(html.Element element) {
|
||||
final result = <Node>[];
|
||||
for (var child in element.children) {
|
||||
result.addAll(
|
||||
_handleListElement(child, {"subtype": StyleKey.bulletedList}));
|
||||
result.addAll(_handleListElement(
|
||||
child, {"subtype": BuiltInAttributeKey.bulletedList}));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -302,7 +303,7 @@ class HTMLToNodesConverter {
|
||||
for (var i = 0; i < element.children.length; i++) {
|
||||
final child = element.children[i];
|
||||
result.addAll(_handleListElement(
|
||||
child, {"subtype": StyleKey.numberList, "number": i + 1}));
|
||||
child, {"subtype": BuiltInAttributeKey.numberList, "number": i + 1}));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -401,7 +402,8 @@ class NodesToHTMLConverter {
|
||||
|
||||
_addElement(TextNode textNode, html.Element element) {
|
||||
if (element.localName == HTMLTag.list) {
|
||||
final isNumbered = textNode.attributes["subtype"] == StyleKey.numberList;
|
||||
final isNumbered =
|
||||
textNode.attributes["subtype"] == BuiltInAttributeKey.numberList;
|
||||
_stashListContainer ??= html.Element.tag(
|
||||
isNumbered ? HTMLTag.orderedList : HTMLTag.unorderedList);
|
||||
_stashListContainer?.append(element);
|
||||
@ -433,10 +435,10 @@ class NodesToHTMLConverter {
|
||||
|
||||
String _textDecorationsFromAttributes(Attributes attributes) {
|
||||
var textDecoration = <String>[];
|
||||
if (attributes[StyleKey.strikethrough] == true) {
|
||||
if (attributes[BuiltInAttributeKey.strikethrough] == true) {
|
||||
textDecoration.add("line-through");
|
||||
}
|
||||
if (attributes[StyleKey.underline] == true) {
|
||||
if (attributes[BuiltInAttributeKey.underline] == true) {
|
||||
textDecoration.add("underline");
|
||||
}
|
||||
|
||||
@ -445,19 +447,19 @@ class NodesToHTMLConverter {
|
||||
|
||||
String _attributesToCssStyle(Map<String, dynamic> attributes) {
|
||||
final cssMap = <String, String>{};
|
||||
if (attributes[StyleKey.backgroundColor] != null) {
|
||||
if (attributes[BuiltInAttributeKey.backgroundColor] != null) {
|
||||
final color = Color(
|
||||
int.parse(attributes[StyleKey.backgroundColor]),
|
||||
int.parse(attributes[BuiltInAttributeKey.backgroundColor]),
|
||||
);
|
||||
cssMap["background-color"] = color.toRgbaString();
|
||||
}
|
||||
if (attributes[StyleKey.color] != null) {
|
||||
if (attributes[BuiltInAttributeKey.color] != null) {
|
||||
final color = Color(
|
||||
int.parse(attributes[StyleKey.color]),
|
||||
int.parse(attributes[BuiltInAttributeKey.color]),
|
||||
);
|
||||
cssMap["color"] = color.toRgbaString();
|
||||
}
|
||||
if (attributes[StyleKey.bold] == true) {
|
||||
if (attributes[BuiltInAttributeKey.bold] == true) {
|
||||
cssMap["font-weight"] = "bold";
|
||||
}
|
||||
|
||||
@ -466,7 +468,7 @@ class NodesToHTMLConverter {
|
||||
cssMap["text-decoration"] = textDecoration;
|
||||
}
|
||||
|
||||
if (attributes[StyleKey.italic] == true) {
|
||||
if (attributes[BuiltInAttributeKey.italic] == true) {
|
||||
cssMap["font-style"] = "italic";
|
||||
}
|
||||
return _cssMapToCssStyle(cssMap);
|
||||
@ -507,23 +509,24 @@ class NodesToHTMLConverter {
|
||||
final childNodes = <html.Node>[];
|
||||
String tagName = HTMLTag.paragraph;
|
||||
|
||||
if (subType == StyleKey.bulletedList || subType == StyleKey.numberList) {
|
||||
if (subType == BuiltInAttributeKey.bulletedList ||
|
||||
subType == BuiltInAttributeKey.numberList) {
|
||||
tagName = HTMLTag.list;
|
||||
} else if (subType == StyleKey.checkbox) {
|
||||
} else if (subType == BuiltInAttributeKey.checkbox) {
|
||||
final node = html.Element.html('<input type="checkbox" />');
|
||||
if (checked != null && checked) {
|
||||
node.attributes["checked"] = "true";
|
||||
}
|
||||
childNodes.add(node);
|
||||
} else if (subType == StyleKey.heading) {
|
||||
if (heading == StyleKey.h1) {
|
||||
} else if (subType == BuiltInAttributeKey.heading) {
|
||||
if (heading == BuiltInAttributeKey.h1) {
|
||||
tagName = HTMLTag.h1;
|
||||
} else if (heading == StyleKey.h2) {
|
||||
} else if (heading == BuiltInAttributeKey.h2) {
|
||||
tagName = HTMLTag.h2;
|
||||
} else if (heading == StyleKey.h3) {
|
||||
} else if (heading == BuiltInAttributeKey.h3) {
|
||||
tagName = HTMLTag.h3;
|
||||
}
|
||||
} else if (subType == StyleKey.quote) {
|
||||
} else if (subType == BuiltInAttributeKey.quote) {
|
||||
tagName = HTMLTag.blockQuote;
|
||||
}
|
||||
|
||||
@ -531,22 +534,23 @@ class NodesToHTMLConverter {
|
||||
if (op is TextInsert) {
|
||||
final attributes = op.attributes;
|
||||
if (attributes != null) {
|
||||
if (attributes.length == 1 && attributes[StyleKey.bold] == true) {
|
||||
if (attributes.length == 1 &&
|
||||
attributes[BuiltInAttributeKey.bold] == true) {
|
||||
final strong = html.Element.tag(HTMLTag.strong);
|
||||
strong.append(html.Text(op.content));
|
||||
childNodes.add(strong);
|
||||
} else if (attributes.length == 1 &&
|
||||
attributes[StyleKey.underline] == true) {
|
||||
attributes[BuiltInAttributeKey.underline] == true) {
|
||||
final strong = html.Element.tag(HTMLTag.underline);
|
||||
strong.append(html.Text(op.content));
|
||||
childNodes.add(strong);
|
||||
} else if (attributes.length == 1 &&
|
||||
attributes[StyleKey.italic] == true) {
|
||||
attributes[BuiltInAttributeKey.italic] == true) {
|
||||
final strong = html.Element.tag(HTMLTag.italic);
|
||||
strong.append(html.Text(op.content));
|
||||
childNodes.add(strong);
|
||||
} else if (attributes.length == 1 &&
|
||||
attributes[StyleKey.strikethrough] == true) {
|
||||
attributes[BuiltInAttributeKey.strikethrough] == true) {
|
||||
final strong = html.Element.tag(HTMLTag.del);
|
||||
strong.append(html.Text(op.content));
|
||||
childNodes.add(strong);
|
||||
|
@ -0,0 +1,126 @@
|
||||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that looks up messages for specific locales by
|
||||
// delegating to the appropriate library.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:implementation_imports, file_names, unnecessary_new
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering
|
||||
// ignore_for_file:argument_type_not_assignable, invalid_assignment
|
||||
// ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases
|
||||
// ignore_for_file:comment_references
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
import 'package:intl/src/intl_helpers.dart';
|
||||
|
||||
import 'messages_ca.dart' as messages_ca;
|
||||
import 'messages_de-DE.dart' as messages_de_de;
|
||||
import 'messages_en.dart' as messages_en;
|
||||
import 'messages_es-VE.dart' as messages_es_ve;
|
||||
import 'messages_fr-CA.dart' as messages_fr_ca;
|
||||
import 'messages_fr-FR.dart' as messages_fr_fr;
|
||||
import 'messages_hu-HU.dart' as messages_hu_hu;
|
||||
import 'messages_id-ID.dart' as messages_id_id;
|
||||
import 'messages_it-IT.dart' as messages_it_it;
|
||||
import 'messages_ja-JP.dart' as messages_ja_jp;
|
||||
import 'messages_pl-PL.dart' as messages_pl_pl;
|
||||
import 'messages_pt-BR.dart' as messages_pt_br;
|
||||
import 'messages_pt-PT.dart' as messages_pt_pt;
|
||||
import 'messages_ru-RU.dart' as messages_ru_ru;
|
||||
import 'messages_tr-TR.dart' as messages_tr_tr;
|
||||
import 'messages_zh-CN.dart' as messages_zh_cn;
|
||||
import 'messages_zh-TW.dart' as messages_zh_tw;
|
||||
|
||||
typedef Future<dynamic> LibraryLoader();
|
||||
Map<String, LibraryLoader> _deferredLibraries = {
|
||||
'ca': () => 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),
|
||||
'hu_HU': () => new Future.value(null),
|
||||
'id_ID': () => new Future.value(null),
|
||||
'it_IT': () => new Future.value(null),
|
||||
'ja_JP': () => 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),
|
||||
};
|
||||
|
||||
MessageLookupByLibrary? _findExact(String localeName) {
|
||||
switch (localeName) {
|
||||
case 'ca':
|
||||
return messages_ca.messages;
|
||||
case 'de_DE':
|
||||
return messages_de_de.messages;
|
||||
case 'en':
|
||||
return messages_en.messages;
|
||||
case 'es_VE':
|
||||
return messages_es_ve.messages;
|
||||
case 'fr_CA':
|
||||
return messages_fr_ca.messages;
|
||||
case 'fr_FR':
|
||||
return messages_fr_fr.messages;
|
||||
case 'hu_HU':
|
||||
return messages_hu_hu.messages;
|
||||
case 'id_ID':
|
||||
return messages_id_id.messages;
|
||||
case 'it_IT':
|
||||
return messages_it_it.messages;
|
||||
case 'ja_JP':
|
||||
return messages_ja_jp.messages;
|
||||
case 'pl_PL':
|
||||
return messages_pl_pl.messages;
|
||||
case 'pt_BR':
|
||||
return messages_pt_br.messages;
|
||||
case 'pt_PT':
|
||||
return messages_pt_pt.messages;
|
||||
case 'ru_RU':
|
||||
return messages_ru_ru.messages;
|
||||
case 'tr_TR':
|
||||
return messages_tr_tr.messages;
|
||||
case 'zh_CN':
|
||||
return messages_zh_cn.messages;
|
||||
case 'zh_TW':
|
||||
return messages_zh_tw.messages;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// User programs should call this before using [localeName] for messages.
|
||||
Future<bool> initializeMessages(String localeName) async {
|
||||
var availableLocale = Intl.verifiedLocale(
|
||||
localeName, (locale) => _deferredLibraries[locale] != null,
|
||||
onFailure: (_) => null);
|
||||
if (availableLocale == null) {
|
||||
return new Future.value(false);
|
||||
}
|
||||
var lib = _deferredLibraries[availableLocale];
|
||||
await (lib == null ? new Future.value(false) : lib());
|
||||
initializeInternalMessageLookup(() => new CompositeMessageLookup());
|
||||
messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor);
|
||||
return new Future.value(true);
|
||||
}
|
||||
|
||||
bool _messagesExistFor(String locale) {
|
||||
try {
|
||||
return _findExact(locale) != null;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
MessageLookupByLibrary? _findGeneratedMessagesFor(String locale) {
|
||||
var actualLocale =
|
||||
Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null);
|
||||
if (actualLocale == null) return null;
|
||||
return _findExact(actualLocale);
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a ca locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'ca';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"bold": MessageLookupByLibrary.simpleMessage(""),
|
||||
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage(""),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage(""),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage(""),
|
||||
"image": MessageLookupByLibrary.simpleMessage(""),
|
||||
"italic": MessageLookupByLibrary.simpleMessage(""),
|
||||
"link": MessageLookupByLibrary.simpleMessage(""),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"quote": MessageLookupByLibrary.simpleMessage(""),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
|
||||
"text": MessageLookupByLibrary.simpleMessage(""),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("")
|
||||
};
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a de_DE locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'de_DE';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"bold": MessageLookupByLibrary.simpleMessage(""),
|
||||
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage(""),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage(""),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage(""),
|
||||
"image": MessageLookupByLibrary.simpleMessage(""),
|
||||
"italic": MessageLookupByLibrary.simpleMessage(""),
|
||||
"link": MessageLookupByLibrary.simpleMessage(""),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"quote": MessageLookupByLibrary.simpleMessage(""),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
|
||||
"text": MessageLookupByLibrary.simpleMessage(""),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("")
|
||||
};
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a en locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'en';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"bold": MessageLookupByLibrary.simpleMessage("Bold"),
|
||||
"bulletedList": MessageLookupByLibrary.simpleMessage("Bulleted List"),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage("Checkbox"),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage("Embed Code"),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage("H1"),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage("H2"),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage("H3"),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage("Highlight"),
|
||||
"image": MessageLookupByLibrary.simpleMessage("Image"),
|
||||
"italic": MessageLookupByLibrary.simpleMessage("Italic"),
|
||||
"link": MessageLookupByLibrary.simpleMessage("Link"),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage("Numbered List"),
|
||||
"quote": MessageLookupByLibrary.simpleMessage("Quote"),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage("Strikethrough"),
|
||||
"text": MessageLookupByLibrary.simpleMessage("Text"),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("Underline")
|
||||
};
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a es_VE locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'es_VE';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"bold": MessageLookupByLibrary.simpleMessage(""),
|
||||
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage(""),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage(""),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage(""),
|
||||
"image": MessageLookupByLibrary.simpleMessage(""),
|
||||
"italic": MessageLookupByLibrary.simpleMessage(""),
|
||||
"link": MessageLookupByLibrary.simpleMessage(""),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"quote": MessageLookupByLibrary.simpleMessage(""),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
|
||||
"text": MessageLookupByLibrary.simpleMessage(""),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("")
|
||||
};
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a fr_CA locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'fr_CA';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"bold": MessageLookupByLibrary.simpleMessage(""),
|
||||
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage(""),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage(""),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage(""),
|
||||
"image": MessageLookupByLibrary.simpleMessage(""),
|
||||
"italic": MessageLookupByLibrary.simpleMessage(""),
|
||||
"link": MessageLookupByLibrary.simpleMessage(""),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"quote": MessageLookupByLibrary.simpleMessage(""),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
|
||||
"text": MessageLookupByLibrary.simpleMessage(""),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("")
|
||||
};
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a fr_FR locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'fr_FR';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"bold": MessageLookupByLibrary.simpleMessage(""),
|
||||
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage(""),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage(""),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage(""),
|
||||
"image": MessageLookupByLibrary.simpleMessage(""),
|
||||
"italic": MessageLookupByLibrary.simpleMessage(""),
|
||||
"link": MessageLookupByLibrary.simpleMessage(""),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"quote": MessageLookupByLibrary.simpleMessage(""),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
|
||||
"text": MessageLookupByLibrary.simpleMessage(""),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("")
|
||||
};
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a hu_HU locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'hu_HU';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"bold": MessageLookupByLibrary.simpleMessage(""),
|
||||
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage(""),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage(""),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage(""),
|
||||
"image": MessageLookupByLibrary.simpleMessage(""),
|
||||
"italic": MessageLookupByLibrary.simpleMessage(""),
|
||||
"link": MessageLookupByLibrary.simpleMessage(""),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"quote": MessageLookupByLibrary.simpleMessage(""),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
|
||||
"text": MessageLookupByLibrary.simpleMessage(""),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("")
|
||||
};
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a id_ID locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'id_ID';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"bold": MessageLookupByLibrary.simpleMessage(""),
|
||||
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage(""),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage(""),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage(""),
|
||||
"image": MessageLookupByLibrary.simpleMessage(""),
|
||||
"italic": MessageLookupByLibrary.simpleMessage(""),
|
||||
"link": MessageLookupByLibrary.simpleMessage(""),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"quote": MessageLookupByLibrary.simpleMessage(""),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
|
||||
"text": MessageLookupByLibrary.simpleMessage(""),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("")
|
||||
};
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a it_IT locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'it_IT';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"bold": MessageLookupByLibrary.simpleMessage(""),
|
||||
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage(""),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage(""),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage(""),
|
||||
"image": MessageLookupByLibrary.simpleMessage(""),
|
||||
"italic": MessageLookupByLibrary.simpleMessage(""),
|
||||
"link": MessageLookupByLibrary.simpleMessage(""),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"quote": MessageLookupByLibrary.simpleMessage(""),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
|
||||
"text": MessageLookupByLibrary.simpleMessage(""),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("")
|
||||
};
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a ja_JP locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'ja_JP';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"bold": MessageLookupByLibrary.simpleMessage(""),
|
||||
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage(""),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage(""),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage(""),
|
||||
"image": MessageLookupByLibrary.simpleMessage(""),
|
||||
"italic": MessageLookupByLibrary.simpleMessage(""),
|
||||
"link": MessageLookupByLibrary.simpleMessage(""),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"quote": MessageLookupByLibrary.simpleMessage(""),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
|
||||
"text": MessageLookupByLibrary.simpleMessage(""),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("")
|
||||
};
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a pl_PL locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'pl_PL';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"bold": MessageLookupByLibrary.simpleMessage(""),
|
||||
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage(""),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage(""),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage(""),
|
||||
"image": MessageLookupByLibrary.simpleMessage(""),
|
||||
"italic": MessageLookupByLibrary.simpleMessage(""),
|
||||
"link": MessageLookupByLibrary.simpleMessage(""),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"quote": MessageLookupByLibrary.simpleMessage(""),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
|
||||
"text": MessageLookupByLibrary.simpleMessage(""),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("")
|
||||
};
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a pt_BR locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'pt_BR';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"bold": MessageLookupByLibrary.simpleMessage(""),
|
||||
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage(""),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage(""),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage(""),
|
||||
"image": MessageLookupByLibrary.simpleMessage(""),
|
||||
"italic": MessageLookupByLibrary.simpleMessage(""),
|
||||
"link": MessageLookupByLibrary.simpleMessage(""),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"quote": MessageLookupByLibrary.simpleMessage(""),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
|
||||
"text": MessageLookupByLibrary.simpleMessage(""),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("")
|
||||
};
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a pt_PT locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'pt_PT';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"bold": MessageLookupByLibrary.simpleMessage(""),
|
||||
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage(""),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage(""),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage(""),
|
||||
"image": MessageLookupByLibrary.simpleMessage(""),
|
||||
"italic": MessageLookupByLibrary.simpleMessage(""),
|
||||
"link": MessageLookupByLibrary.simpleMessage(""),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"quote": MessageLookupByLibrary.simpleMessage(""),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
|
||||
"text": MessageLookupByLibrary.simpleMessage(""),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("")
|
||||
};
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a ru_RU locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'ru_RU';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"bold": MessageLookupByLibrary.simpleMessage(""),
|
||||
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage(""),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage(""),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage(""),
|
||||
"image": MessageLookupByLibrary.simpleMessage(""),
|
||||
"italic": MessageLookupByLibrary.simpleMessage(""),
|
||||
"link": MessageLookupByLibrary.simpleMessage(""),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"quote": MessageLookupByLibrary.simpleMessage(""),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
|
||||
"text": MessageLookupByLibrary.simpleMessage(""),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("")
|
||||
};
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a tr_TR locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'tr_TR';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"bold": MessageLookupByLibrary.simpleMessage(""),
|
||||
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage(""),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage(""),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage(""),
|
||||
"image": MessageLookupByLibrary.simpleMessage(""),
|
||||
"italic": MessageLookupByLibrary.simpleMessage(""),
|
||||
"link": MessageLookupByLibrary.simpleMessage(""),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"quote": MessageLookupByLibrary.simpleMessage(""),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
|
||||
"text": MessageLookupByLibrary.simpleMessage(""),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("")
|
||||
};
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a zh_CN locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'zh_CN';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"bold": MessageLookupByLibrary.simpleMessage("加粗"),
|
||||
"bulletedList": MessageLookupByLibrary.simpleMessage("无序列表"),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage("复选框"),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage("内嵌代码"),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage("标题 1"),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage("标题 2"),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage("标题 3"),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage("高亮"),
|
||||
"image": MessageLookupByLibrary.simpleMessage("图片"),
|
||||
"italic": MessageLookupByLibrary.simpleMessage("斜体"),
|
||||
"link": MessageLookupByLibrary.simpleMessage("链接"),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage("有序列表"),
|
||||
"quote": MessageLookupByLibrary.simpleMessage("引用"),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage("删除线"),
|
||||
"text": MessageLookupByLibrary.simpleMessage("文字"),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("下划线")
|
||||
};
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a zh_TW locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'zh_TW';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"bold": MessageLookupByLibrary.simpleMessage(""),
|
||||
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage(""),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage(""),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage(""),
|
||||
"image": MessageLookupByLibrary.simpleMessage(""),
|
||||
"italic": MessageLookupByLibrary.simpleMessage(""),
|
||||
"link": MessageLookupByLibrary.simpleMessage(""),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"quote": MessageLookupByLibrary.simpleMessage(""),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
|
||||
"text": MessageLookupByLibrary.simpleMessage(""),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("")
|
||||
};
|
||||
}
|
@ -0,0 +1,257 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'intl/messages_all.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// Generator: Flutter Intl IDE plugin
|
||||
// Made by Localizely
|
||||
// **************************************************************************
|
||||
|
||||
// ignore_for_file: non_constant_identifier_names, lines_longer_than_80_chars
|
||||
// ignore_for_file: join_return_with_assignment, prefer_final_in_for_each
|
||||
// ignore_for_file: avoid_redundant_argument_values, avoid_escaping_inner_quotes
|
||||
|
||||
class AppFlowyEditorLocalizations {
|
||||
AppFlowyEditorLocalizations();
|
||||
|
||||
static AppFlowyEditorLocalizations? _current;
|
||||
|
||||
static AppFlowyEditorLocalizations get current {
|
||||
assert(_current != null,
|
||||
'No instance of AppFlowyEditorLocalizations was loaded. Try to initialize the AppFlowyEditorLocalizations delegate before accessing AppFlowyEditorLocalizations.current.');
|
||||
return _current!;
|
||||
}
|
||||
|
||||
static const AppLocalizationDelegate delegate = AppLocalizationDelegate();
|
||||
|
||||
static Future<AppFlowyEditorLocalizations> load(Locale locale) {
|
||||
final name = (locale.countryCode?.isEmpty ?? false)
|
||||
? locale.languageCode
|
||||
: locale.toString();
|
||||
final localeName = Intl.canonicalizedLocale(name);
|
||||
return initializeMessages(localeName).then((_) {
|
||||
Intl.defaultLocale = localeName;
|
||||
final instance = AppFlowyEditorLocalizations();
|
||||
AppFlowyEditorLocalizations._current = instance;
|
||||
|
||||
return instance;
|
||||
});
|
||||
}
|
||||
|
||||
static AppFlowyEditorLocalizations of(BuildContext context) {
|
||||
final instance = AppFlowyEditorLocalizations.maybeOf(context);
|
||||
assert(instance != null,
|
||||
'No instance of AppFlowyEditorLocalizations present in the widget tree. Did you add AppFlowyEditorLocalizations.delegate in localizationsDelegates?');
|
||||
return instance!;
|
||||
}
|
||||
|
||||
static AppFlowyEditorLocalizations? maybeOf(BuildContext context) {
|
||||
return Localizations.of<AppFlowyEditorLocalizations>(
|
||||
context, AppFlowyEditorLocalizations);
|
||||
}
|
||||
|
||||
/// `Bold`
|
||||
String get bold {
|
||||
return Intl.message(
|
||||
'Bold',
|
||||
name: 'bold',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Bulleted List`
|
||||
String get bulletedList {
|
||||
return Intl.message(
|
||||
'Bulleted List',
|
||||
name: 'bulletedList',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Checkbox`
|
||||
String get checkbox {
|
||||
return Intl.message(
|
||||
'Checkbox',
|
||||
name: 'checkbox',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Embed Code`
|
||||
String get embedCode {
|
||||
return Intl.message(
|
||||
'Embed Code',
|
||||
name: 'embedCode',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `H1`
|
||||
String get heading1 {
|
||||
return Intl.message(
|
||||
'H1',
|
||||
name: 'heading1',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `H2`
|
||||
String get heading2 {
|
||||
return Intl.message(
|
||||
'H2',
|
||||
name: 'heading2',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `H3`
|
||||
String get heading3 {
|
||||
return Intl.message(
|
||||
'H3',
|
||||
name: 'heading3',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Highlight`
|
||||
String get highlight {
|
||||
return Intl.message(
|
||||
'Highlight',
|
||||
name: 'highlight',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Image`
|
||||
String get image {
|
||||
return Intl.message(
|
||||
'Image',
|
||||
name: 'image',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Italic`
|
||||
String get italic {
|
||||
return Intl.message(
|
||||
'Italic',
|
||||
name: 'italic',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Link`
|
||||
String get link {
|
||||
return Intl.message(
|
||||
'Link',
|
||||
name: 'link',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Numbered List`
|
||||
String get numberedList {
|
||||
return Intl.message(
|
||||
'Numbered List',
|
||||
name: 'numberedList',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Quote`
|
||||
String get quote {
|
||||
return Intl.message(
|
||||
'Quote',
|
||||
name: 'quote',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Strikethrough`
|
||||
String get strikethrough {
|
||||
return Intl.message(
|
||||
'Strikethrough',
|
||||
name: 'strikethrough',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Text`
|
||||
String get text {
|
||||
return Intl.message(
|
||||
'Text',
|
||||
name: 'text',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Underline`
|
||||
String get underline {
|
||||
return Intl.message(
|
||||
'Underline',
|
||||
name: 'underline',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppLocalizationDelegate
|
||||
extends LocalizationsDelegate<AppFlowyEditorLocalizations> {
|
||||
const AppLocalizationDelegate();
|
||||
|
||||
List<Locale> get supportedLocales {
|
||||
return const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'en'),
|
||||
Locale.fromSubtags(languageCode: 'ca'),
|
||||
Locale.fromSubtags(languageCode: 'de', countryCode: 'DE'),
|
||||
Locale.fromSubtags(languageCode: 'es', countryCode: 'VE'),
|
||||
Locale.fromSubtags(languageCode: 'fr', countryCode: 'CA'),
|
||||
Locale.fromSubtags(languageCode: 'fr', countryCode: 'FR'),
|
||||
Locale.fromSubtags(languageCode: 'hu', countryCode: 'HU'),
|
||||
Locale.fromSubtags(languageCode: 'id', countryCode: 'ID'),
|
||||
Locale.fromSubtags(languageCode: 'it', countryCode: 'IT'),
|
||||
Locale.fromSubtags(languageCode: 'ja', countryCode: 'JP'),
|
||||
Locale.fromSubtags(languageCode: 'pl', countryCode: 'PL'),
|
||||
Locale.fromSubtags(languageCode: 'pt', countryCode: 'BR'),
|
||||
Locale.fromSubtags(languageCode: 'pt', countryCode: 'PT'),
|
||||
Locale.fromSubtags(languageCode: 'ru', countryCode: 'RU'),
|
||||
Locale.fromSubtags(languageCode: 'tr', countryCode: 'TR'),
|
||||
Locale.fromSubtags(languageCode: 'zh', countryCode: 'CN'),
|
||||
Locale.fromSubtags(languageCode: 'zh', countryCode: 'TW'),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
bool isSupported(Locale locale) => _isSupported(locale);
|
||||
@override
|
||||
Future<AppFlowyEditorLocalizations> load(Locale locale) =>
|
||||
AppFlowyEditorLocalizations.load(locale);
|
||||
@override
|
||||
bool shouldReload(AppLocalizationDelegate old) => false;
|
||||
|
||||
bool _isSupported(Locale locale) {
|
||||
for (var supportedLocale in supportedLocales) {
|
||||
if (supportedLocale.languageCode == locale.languageCode) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
abstract class BuiltInTextWidget extends StatefulWidget {
|
||||
const BuiltInTextWidget({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
EditorState get editorState;
|
||||
TextNode get textNode;
|
||||
}
|
||||
|
||||
mixin BuiltInStyleMixin<T extends BuiltInTextWidget> on State<T> {
|
||||
EdgeInsets get padding {
|
||||
final padding = widget.editorState.editorStyle.style(
|
||||
widget.editorState,
|
||||
widget.textNode,
|
||||
'padding',
|
||||
);
|
||||
if (padding is EdgeInsets) {
|
||||
return padding;
|
||||
}
|
||||
return const EdgeInsets.all(0);
|
||||
}
|
||||
|
||||
TextStyle get textStyle {
|
||||
final textStyle = widget.editorState.editorStyle.style(
|
||||
widget.editorState,
|
||||
widget.textNode,
|
||||
'textStyle',
|
||||
);
|
||||
if (textStyle is TextStyle) {
|
||||
return textStyle;
|
||||
}
|
||||
return const TextStyle();
|
||||
}
|
||||
|
||||
Size? get iconSize {
|
||||
final iconSize = widget.editorState.editorStyle.style(
|
||||
widget.editorState,
|
||||
widget.textNode,
|
||||
'iconSize',
|
||||
);
|
||||
if (iconSize is Size) {
|
||||
return iconSize;
|
||||
}
|
||||
return const Size.square(18.0);
|
||||
}
|
||||
|
||||
EdgeInsets? get iconPadding {
|
||||
final iconPadding = widget.editorState.editorStyle.style(
|
||||
widget.editorState,
|
||||
widget.textNode,
|
||||
'iconPadding',
|
||||
);
|
||||
if (iconPadding is EdgeInsets) {
|
||||
return iconPadding;
|
||||
}
|
||||
return const EdgeInsets.all(0);
|
||||
}
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
import 'package:appflowy_editor/src/document/node.dart';
|
||||
import 'package:appflowy_editor/src/editor_state.dart';
|
||||
import 'package:appflowy_editor/src/infra/flowy_svg.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/built_in_text_widget.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/default_selectable.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/flowy_rich_text.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||
import 'package:appflowy_editor/src/render/selection/selectable.dart';
|
||||
import 'package:appflowy_editor/src/service/render_plugin_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -24,14 +24,16 @@ class BulletedListTextNodeWidgetBuilder extends NodeWidgetBuilder<TextNode> {
|
||||
});
|
||||
}
|
||||
|
||||
class BulletedListTextNodeWidget extends StatefulWidget {
|
||||
class BulletedListTextNodeWidget extends BuiltInTextWidget {
|
||||
const BulletedListTextNodeWidget({
|
||||
Key? key,
|
||||
required this.textNode,
|
||||
required this.editorState,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
final TextNode textNode;
|
||||
@override
|
||||
final EditorState editorState;
|
||||
|
||||
@override
|
||||
@ -42,36 +44,40 @@ class BulletedListTextNodeWidget extends StatefulWidget {
|
||||
// customize
|
||||
|
||||
class _BulletedListTextNodeWidgetState extends State<BulletedListTextNodeWidget>
|
||||
with SelectableMixin, DefaultSelectable {
|
||||
with SelectableMixin, DefaultSelectable, BuiltInStyleMixin {
|
||||
@override
|
||||
final iconKey = GlobalKey();
|
||||
|
||||
final _richTextKey = GlobalKey(debugLabel: 'bulleted_list_text');
|
||||
final _iconWidth = 20.0;
|
||||
final _iconRightPadding = 5.0;
|
||||
|
||||
@override
|
||||
SelectableMixin<StatefulWidget> get forward =>
|
||||
_richTextKey.currentState as SelectableMixin;
|
||||
|
||||
@override
|
||||
Offset get baseOffset {
|
||||
return super.baseOffset.translate(0, padding.top);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: defaultLinePadding),
|
||||
padding: padding,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
FlowySvg(
|
||||
key: iconKey,
|
||||
width: _iconWidth,
|
||||
height: _iconWidth,
|
||||
padding: EdgeInsets.only(right: _iconRightPadding),
|
||||
width: iconSize?.width,
|
||||
height: iconSize?.height,
|
||||
padding: iconPadding,
|
||||
name: 'point',
|
||||
),
|
||||
Flexible(
|
||||
child: FlowyRichText(
|
||||
key: _richTextKey,
|
||||
placeholderText: 'List',
|
||||
lineHeight: widget.editorState.editorStyle.textStyle.lineHeight,
|
||||
textNode: widget.textNode,
|
||||
editorState: widget.editorState,
|
||||
),
|
||||
|
@ -1,12 +1,16 @@
|
||||
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
||||
import 'package:appflowy_editor/src/document/node.dart';
|
||||
import 'package:appflowy_editor/src/editor_state.dart';
|
||||
import 'package:appflowy_editor/src/infra/flowy_svg.dart';
|
||||
import 'package:appflowy_editor/src/operation/transaction_builder.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/built_in_text_widget.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/default_selectable.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/flowy_rich_text.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||
import 'package:appflowy_editor/src/render/selection/selectable.dart';
|
||||
import 'package:appflowy_editor/src/service/default_text_operations/format_rich_text_style.dart';
|
||||
|
||||
import 'package:appflowy_editor/src/service/render_plugin_service.dart';
|
||||
import 'package:appflowy_editor/src/extensions/attributes_extension.dart';
|
||||
import 'package:appflowy_editor/src/extensions/text_style_extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CheckboxNodeWidgetBuilder extends NodeWidgetBuilder<TextNode> {
|
||||
@ -21,18 +25,20 @@ class CheckboxNodeWidgetBuilder extends NodeWidgetBuilder<TextNode> {
|
||||
|
||||
@override
|
||||
NodeValidator<Node> get nodeValidator => ((node) {
|
||||
return node.attributes.containsKey(StyleKey.checkbox);
|
||||
return node.attributes.containsKey(BuiltInAttributeKey.checkbox);
|
||||
});
|
||||
}
|
||||
|
||||
class CheckboxNodeWidget extends StatefulWidget {
|
||||
class CheckboxNodeWidget extends BuiltInTextWidget {
|
||||
const CheckboxNodeWidget({
|
||||
Key? key,
|
||||
required this.textNode,
|
||||
required this.editorState,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
final TextNode textNode;
|
||||
@override
|
||||
final EditorState editorState;
|
||||
|
||||
@override
|
||||
@ -40,18 +46,21 @@ class CheckboxNodeWidget extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
|
||||
with SelectableMixin, DefaultSelectable {
|
||||
with SelectableMixin, DefaultSelectable, BuiltInStyleMixin {
|
||||
@override
|
||||
final iconKey = GlobalKey();
|
||||
|
||||
final _richTextKey = GlobalKey(debugLabel: 'checkbox_text');
|
||||
final _iconWidth = 20.0;
|
||||
final _iconRightPadding = 5.0;
|
||||
|
||||
@override
|
||||
SelectableMixin<StatefulWidget> get forward =>
|
||||
_richTextKey.currentState as SelectableMixin;
|
||||
|
||||
@override
|
||||
Offset get baseOffset {
|
||||
return super.baseOffset.translate(0, padding.top);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.textNode.children.isEmpty) {
|
||||
@ -64,33 +73,32 @@ class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
|
||||
Widget _buildWithSingle(BuildContext context) {
|
||||
final check = widget.textNode.attributes.check;
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: defaultLinePadding),
|
||||
padding: padding,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
GestureDetector(
|
||||
key: iconKey,
|
||||
child: FlowySvg(
|
||||
width: _iconWidth,
|
||||
height: _iconWidth,
|
||||
padding: EdgeInsets.only(right: _iconRightPadding),
|
||||
width: iconSize?.width,
|
||||
height: iconSize?.height,
|
||||
padding: iconPadding,
|
||||
name: check ? 'check' : 'uncheck',
|
||||
),
|
||||
onTap: () {
|
||||
TransactionBuilder(widget.editorState)
|
||||
..updateNode(widget.textNode, {
|
||||
StyleKey.checkbox: !check,
|
||||
})
|
||||
..commit();
|
||||
formatCheckbox(widget.editorState, !check);
|
||||
},
|
||||
),
|
||||
Flexible(
|
||||
child: FlowyRichText(
|
||||
key: _richTextKey,
|
||||
placeholderText: 'To-do',
|
||||
lineHeight: widget.editorState.editorStyle.textStyle.lineHeight,
|
||||
textNode: widget.textNode,
|
||||
textSpanDecorator: _textSpanDecorator,
|
||||
placeholderTextSpanDecorator: _textSpanDecorator,
|
||||
textSpanDecorator: (textSpan) =>
|
||||
textSpan.updateTextStyle(textStyle),
|
||||
placeholderTextSpanDecorator: (textSpan) =>
|
||||
textSpan.updateTextStyle(textStyle),
|
||||
editorState: widget.editorState,
|
||||
),
|
||||
),
|
||||
@ -134,28 +142,4 @@ class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
TextSpan _textSpanDecorator(TextSpan textSpan) {
|
||||
return TextSpan(
|
||||
children: textSpan.children
|
||||
?.whereType<TextSpan>()
|
||||
.map(
|
||||
(span) => TextSpan(
|
||||
text: span.text,
|
||||
style: widget.textNode.attributes.check
|
||||
? span.style?.copyWith(
|
||||
color: Colors.grey,
|
||||
decoration: TextDecoration.combine([
|
||||
TextDecoration.lineThrough,
|
||||
if (span.style?.decoration != null)
|
||||
span.style!.decoration!
|
||||
]),
|
||||
)
|
||||
: span.style,
|
||||
recognizer: span.recognizer,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:appflowy_editor/src/extensions/url_launcher_extension.dart';
|
||||
import 'package:appflowy_editor/src/render/toolbar/toolbar_item.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
@ -13,8 +11,12 @@ import 'package:appflowy_editor/src/document/position.dart';
|
||||
import 'package:appflowy_editor/src/document/selection.dart';
|
||||
import 'package:appflowy_editor/src/document/text_delta.dart';
|
||||
import 'package:appflowy_editor/src/editor_state.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||
import 'package:appflowy_editor/src/extensions/url_launcher_extension.dart';
|
||||
import 'package:appflowy_editor/src/extensions/text_style_extension.dart';
|
||||
import 'package:appflowy_editor/src/extensions/attributes_extension.dart';
|
||||
|
||||
import 'package:appflowy_editor/src/render/selection/selectable.dart';
|
||||
import 'package:appflowy_editor/src/render/toolbar/toolbar_item.dart';
|
||||
|
||||
typedef FlowyTextSpanDecorator = TextSpan Function(TextSpan textSpan);
|
||||
|
||||
@ -23,6 +25,7 @@ class FlowyRichText extends StatefulWidget {
|
||||
Key? key,
|
||||
this.cursorHeight,
|
||||
this.cursorWidth = 1.0,
|
||||
this.lineHeight = 1.0,
|
||||
this.textSpanDecorator,
|
||||
this.placeholderText = ' ',
|
||||
this.placeholderTextSpanDecorator,
|
||||
@ -34,6 +37,7 @@ class FlowyRichText extends StatefulWidget {
|
||||
final EditorState editorState;
|
||||
final double? cursorHeight;
|
||||
final double cursorWidth;
|
||||
final double lineHeight;
|
||||
final FlowyTextSpanDecorator? textSpanDecorator;
|
||||
final String placeholderText;
|
||||
final FlowyTextSpanDecorator? placeholderTextSpanDecorator;
|
||||
@ -46,8 +50,6 @@ class _FlowyRichTextState extends State<FlowyRichText> with SelectableMixin {
|
||||
var _textKey = GlobalKey();
|
||||
final _placeholderTextKey = GlobalKey();
|
||||
|
||||
final _lineHeight = 1.5;
|
||||
|
||||
RenderParagraph get _renderParagraph =>
|
||||
_textKey.currentContext?.findRenderObject() as RenderParagraph;
|
||||
|
||||
@ -90,20 +92,6 @@ class _FlowyRichTextState extends State<FlowyRichText> with SelectableMixin {
|
||||
cursorOffset = _placeholderRenderParagraph.getOffsetForCaret(
|
||||
textPosition, Rect.zero);
|
||||
}
|
||||
if (cursorHeight != null) {
|
||||
// workaround: Calling the `getFullHeightForCaret` function will return
|
||||
// the full height of rich text component instead of the plain text
|
||||
// if we set the line height.
|
||||
// So need to divide by the line height to get the expected value.
|
||||
//
|
||||
// And the default height of plain text is too short. Add a magic height
|
||||
// to expand it.
|
||||
const magicHeight = 3.0;
|
||||
cursorOffset = cursorOffset.translate(
|
||||
0, (cursorHeight - cursorHeight / _lineHeight) / 2.0);
|
||||
cursorHeight /= _lineHeight;
|
||||
cursorHeight += magicHeight;
|
||||
}
|
||||
final rect = Rect.fromLTWH(
|
||||
cursorOffset.dx - (widget.cursorWidth / 2),
|
||||
cursorOffset.dy,
|
||||
@ -190,8 +178,8 @@ class _FlowyRichTextState extends State<FlowyRichText> with SelectableMixin {
|
||||
key: _placeholderTextKey,
|
||||
textHeightBehavior: const TextHeightBehavior(
|
||||
applyHeightToFirstAscent: false, applyHeightToLastDescent: false),
|
||||
text: widget.textSpanDecorator != null
|
||||
? widget.textSpanDecorator!(textSpan)
|
||||
text: widget.placeholderTextSpanDecorator != null
|
||||
? widget.placeholderTextSpanDecorator!(textSpan)
|
||||
: textSpan,
|
||||
);
|
||||
}
|
||||
@ -210,42 +198,73 @@ class _FlowyRichTextState extends State<FlowyRichText> with SelectableMixin {
|
||||
);
|
||||
}
|
||||
|
||||
TextSpan get _textSpan {
|
||||
var offset = 0;
|
||||
TextSpan get _placeholderTextSpan {
|
||||
final style = widget.editorState.editorStyle.textStyle;
|
||||
return TextSpan(
|
||||
children: widget.textNode.delta.whereType<TextInsert>().map((insert) {
|
||||
GestureRecognizer? gestureRecognizer;
|
||||
if (insert.attributes?[StyleKey.href] != null) {
|
||||
gestureRecognizer = _buildTapHrefGestureRecognizer(
|
||||
insert.attributes![StyleKey.href],
|
||||
Selection.single(
|
||||
path: widget.textNode.path,
|
||||
startOffset: offset,
|
||||
endOffset: offset + insert.length,
|
||||
),
|
||||
);
|
||||
}
|
||||
offset += insert.length;
|
||||
final textSpan = RichTextStyle(
|
||||
attributes: insert.attributes ?? {},
|
||||
text: insert.content,
|
||||
height: _lineHeight,
|
||||
gestureRecognizer: gestureRecognizer,
|
||||
).toTextSpan();
|
||||
return textSpan;
|
||||
}).toList(growable: false),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: widget.placeholderText,
|
||||
style: style.defaultPlaceholderTextStyle,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
TextSpan get _placeholderTextSpan => TextSpan(children: [
|
||||
RichTextStyle(
|
||||
text: widget.placeholderText,
|
||||
attributes: {
|
||||
StyleKey.color: '0xFF707070',
|
||||
},
|
||||
height: _lineHeight,
|
||||
).toTextSpan()
|
||||
]);
|
||||
TextSpan get _textSpan {
|
||||
var offset = 0;
|
||||
List<TextSpan> textSpans = [];
|
||||
final style = widget.editorState.editorStyle.textStyle;
|
||||
final textInserts = widget.textNode.delta.whereType<TextInsert>();
|
||||
for (final textInsert in textInserts) {
|
||||
var textStyle = style.defaultTextStyle;
|
||||
GestureRecognizer? recognizer;
|
||||
final attributes = textInsert.attributes;
|
||||
if (attributes != null) {
|
||||
if (attributes.bold == true) {
|
||||
textStyle = textStyle.combine(style.bold);
|
||||
}
|
||||
if (attributes.italic == true) {
|
||||
textStyle = textStyle.combine(style.italic);
|
||||
}
|
||||
if (attributes.underline == true) {
|
||||
textStyle = textStyle.combine(style.underline);
|
||||
}
|
||||
if (attributes.strikethrough == true) {
|
||||
textStyle = textStyle.combine(style.strikethrough);
|
||||
}
|
||||
if (attributes.href != null) {
|
||||
textStyle = textStyle.combine(style.href);
|
||||
recognizer = _buildTapHrefGestureRecognizer(
|
||||
attributes.href!,
|
||||
Selection.single(
|
||||
path: widget.textNode.path,
|
||||
startOffset: offset,
|
||||
endOffset: offset + textInsert.length,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (attributes.code == true) {
|
||||
textStyle = textStyle.combine(style.code);
|
||||
}
|
||||
if (attributes.backgroundColor != null) {
|
||||
textStyle = textStyle.combine(
|
||||
TextStyle(backgroundColor: attributes.backgroundColor),
|
||||
);
|
||||
}
|
||||
}
|
||||
offset += textInsert.length;
|
||||
textSpans.add(
|
||||
TextSpan(
|
||||
text: textInsert.content,
|
||||
style: textStyle,
|
||||
recognizer: recognizer,
|
||||
),
|
||||
);
|
||||
}
|
||||
return TextSpan(
|
||||
children: textSpans,
|
||||
);
|
||||
}
|
||||
|
||||
GestureRecognizer _buildTapHrefGestureRecognizer(
|
||||
String href, Selection selection) {
|
||||
|
@ -1,11 +1,13 @@
|
||||
import 'package:appflowy_editor/src/document/node.dart';
|
||||
import 'package:appflowy_editor/src/editor_state.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/built_in_text_widget.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/default_selectable.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/flowy_rich_text.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||
import 'package:appflowy_editor/src/render/selection/selectable.dart';
|
||||
import 'package:appflowy_editor/src/service/render_plugin_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:appflowy_editor/src/extensions/attributes_extension.dart';
|
||||
import 'package:appflowy_editor/src/extensions/text_style_extension.dart';
|
||||
|
||||
class HeadingTextNodeWidgetBuilder extends NodeWidgetBuilder<TextNode> {
|
||||
@override
|
||||
@ -23,14 +25,16 @@ class HeadingTextNodeWidgetBuilder extends NodeWidgetBuilder<TextNode> {
|
||||
});
|
||||
}
|
||||
|
||||
class HeadingTextNodeWidget extends StatefulWidget {
|
||||
class HeadingTextNodeWidget extends BuiltInTextWidget {
|
||||
const HeadingTextNodeWidget({
|
||||
Key? key,
|
||||
required this.textNode,
|
||||
required this.editorState,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
final TextNode textNode;
|
||||
@override
|
||||
final EditorState editorState;
|
||||
|
||||
@override
|
||||
@ -39,12 +43,11 @@ class HeadingTextNodeWidget extends StatefulWidget {
|
||||
|
||||
// customize
|
||||
class _HeadingTextNodeWidgetState extends State<HeadingTextNodeWidget>
|
||||
with SelectableMixin, DefaultSelectable {
|
||||
with SelectableMixin, DefaultSelectable, BuiltInStyleMixin {
|
||||
@override
|
||||
GlobalKey? get iconKey => null;
|
||||
|
||||
final _richTextKey = GlobalKey(debugLabel: 'heading_text');
|
||||
final _topPadding = 5.0;
|
||||
|
||||
@override
|
||||
SelectableMixin<StatefulWidget> get forward =>
|
||||
@ -52,58 +55,23 @@ class _HeadingTextNodeWidgetState extends State<HeadingTextNodeWidget>
|
||||
|
||||
@override
|
||||
Offset get baseOffset {
|
||||
return Offset(0, _topPadding);
|
||||
return padding.topLeft;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: _topPadding,
|
||||
bottom: defaultLinePadding,
|
||||
),
|
||||
padding: padding,
|
||||
child: FlowyRichText(
|
||||
key: _richTextKey,
|
||||
placeholderText: 'Heading',
|
||||
placeholderTextSpanDecorator: _placeholderTextSpanDecorator,
|
||||
textSpanDecorator: _textSpanDecorator,
|
||||
placeholderTextSpanDecorator: (textSpan) =>
|
||||
textSpan.updateTextStyle(textStyle),
|
||||
textSpanDecorator: (textSpan) => textSpan.updateTextStyle(textStyle),
|
||||
lineHeight: widget.editorState.editorStyle.textStyle.lineHeight,
|
||||
textNode: widget.textNode,
|
||||
editorState: widget.editorState,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TextSpan _textSpanDecorator(TextSpan textSpan) {
|
||||
return TextSpan(
|
||||
children: textSpan.children
|
||||
?.whereType<TextSpan>()
|
||||
.map(
|
||||
(span) => TextSpan(
|
||||
text: span.text,
|
||||
style: span.style?.copyWith(
|
||||
fontSize: widget.textNode.attributes.fontSize,
|
||||
),
|
||||
recognizer: span.recognizer,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
TextSpan _placeholderTextSpanDecorator(TextSpan textSpan) {
|
||||
return TextSpan(
|
||||
children: textSpan.children
|
||||
?.whereType<TextSpan>()
|
||||
.map(
|
||||
(span) => TextSpan(
|
||||
text: span.text,
|
||||
style: span.style?.copyWith(
|
||||
fontSize: widget.textNode.attributes.fontSize,
|
||||
),
|
||||
recognizer: span.recognizer,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
import 'package:appflowy_editor/src/document/node.dart';
|
||||
import 'package:appflowy_editor/src/editor_state.dart';
|
||||
import 'package:appflowy_editor/src/infra/flowy_svg.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/built_in_text_widget.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/default_selectable.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/flowy_rich_text.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||
import 'package:appflowy_editor/src/render/selection/selectable.dart';
|
||||
import 'package:appflowy_editor/src/service/render_plugin_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:appflowy_editor/src/extensions/attributes_extension.dart';
|
||||
import 'package:appflowy_editor/src/extensions/text_style_extension.dart';
|
||||
|
||||
class NumberListTextNodeWidgetBuilder extends NodeWidgetBuilder<TextNode> {
|
||||
@override
|
||||
@ -24,14 +25,16 @@ class NumberListTextNodeWidgetBuilder extends NodeWidgetBuilder<TextNode> {
|
||||
});
|
||||
}
|
||||
|
||||
class NumberListTextNodeWidget extends StatefulWidget {
|
||||
class NumberListTextNodeWidget extends BuiltInTextWidget {
|
||||
const NumberListTextNodeWidget({
|
||||
Key? key,
|
||||
required this.textNode,
|
||||
required this.editorState,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
final TextNode textNode;
|
||||
@override
|
||||
final EditorState editorState;
|
||||
|
||||
@override
|
||||
@ -39,11 +42,8 @@ class NumberListTextNodeWidget extends StatefulWidget {
|
||||
_NumberListTextNodeWidgetState();
|
||||
}
|
||||
|
||||
// customize
|
||||
const double _numberHorizontalPadding = 8;
|
||||
|
||||
class _NumberListTextNodeWidgetState extends State<NumberListTextNodeWidget>
|
||||
with SelectableMixin, DefaultSelectable {
|
||||
with SelectableMixin, DefaultSelectable, BuiltInStyleMixin {
|
||||
@override
|
||||
final iconKey = GlobalKey();
|
||||
|
||||
@ -53,31 +53,42 @@ class _NumberListTextNodeWidgetState extends State<NumberListTextNodeWidget>
|
||||
SelectableMixin<StatefulWidget> get forward =>
|
||||
_richTextKey.currentState as SelectableMixin;
|
||||
|
||||
@override
|
||||
Offset get baseOffset {
|
||||
return super.baseOffset.translate(0, padding.top);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: defaultLinePadding),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
key: iconKey,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: _numberHorizontalPadding, vertical: 0),
|
||||
child: Text(
|
||||
'${widget.textNode.attributes.number.toString()}.',
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
padding: padding,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
key: iconKey,
|
||||
padding: iconPadding,
|
||||
child: Text(
|
||||
'${widget.textNode.attributes.number.toString()}.',
|
||||
// FIXME: customize
|
||||
style: const TextStyle(fontSize: 16.0, color: Colors.black),
|
||||
),
|
||||
Flexible(
|
||||
child: FlowyRichText(
|
||||
key: _richTextKey,
|
||||
placeholderText: 'List',
|
||||
textNode: widget.textNode,
|
||||
editorState: widget.editorState,
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: FlowyRichText(
|
||||
key: _richTextKey,
|
||||
placeholderText: 'List',
|
||||
textNode: widget.textNode,
|
||||
editorState: widget.editorState,
|
||||
lineHeight: widget.editorState.editorStyle.textStyle.lineHeight,
|
||||
placeholderTextSpanDecorator: (textSpan) =>
|
||||
textSpan.updateTextStyle(textStyle),
|
||||
textSpanDecorator: (textSpan) =>
|
||||
textSpan.updateTextStyle(textStyle),
|
||||
),
|
||||
],
|
||||
));
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import 'package:appflowy_editor/src/document/node.dart';
|
||||
import 'package:appflowy_editor/src/editor_state.dart';
|
||||
import 'package:appflowy_editor/src/infra/flowy_svg.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/built_in_text_widget.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/default_selectable.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/flowy_rich_text.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||
import 'package:appflowy_editor/src/render/selection/selectable.dart';
|
||||
import 'package:appflowy_editor/src/service/render_plugin_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -24,14 +24,16 @@ class QuotedTextNodeWidgetBuilder extends NodeWidgetBuilder<TextNode> {
|
||||
});
|
||||
}
|
||||
|
||||
class QuotedTextNodeWidget extends StatefulWidget {
|
||||
class QuotedTextNodeWidget extends BuiltInTextWidget {
|
||||
const QuotedTextNodeWidget({
|
||||
Key? key,
|
||||
required this.textNode,
|
||||
required this.editorState,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
final TextNode textNode;
|
||||
@override
|
||||
final EditorState editorState;
|
||||
|
||||
@override
|
||||
@ -41,30 +43,33 @@ class QuotedTextNodeWidget extends StatefulWidget {
|
||||
// customize
|
||||
|
||||
class _QuotedTextNodeWidgetState extends State<QuotedTextNodeWidget>
|
||||
with SelectableMixin, DefaultSelectable {
|
||||
with SelectableMixin, DefaultSelectable, BuiltInStyleMixin {
|
||||
@override
|
||||
final iconKey = GlobalKey();
|
||||
|
||||
final _richTextKey = GlobalKey(debugLabel: 'quoted_text');
|
||||
final _iconWidth = 20.0;
|
||||
final _iconRightPadding = 5.0;
|
||||
|
||||
@override
|
||||
SelectableMixin<StatefulWidget> get forward =>
|
||||
_richTextKey.currentState as SelectableMixin;
|
||||
|
||||
@override
|
||||
Offset get baseOffset {
|
||||
return super.baseOffset.translate(0, padding.top);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: defaultLinePadding),
|
||||
padding: padding,
|
||||
child: IntrinsicHeight(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
FlowySvg(
|
||||
key: iconKey,
|
||||
width: _iconWidth,
|
||||
padding: EdgeInsets.only(right: _iconRightPadding),
|
||||
width: iconSize?.width,
|
||||
padding: iconPadding,
|
||||
name: 'quote',
|
||||
),
|
||||
Flexible(
|
||||
@ -72,6 +77,7 @@ class _QuotedTextNodeWidgetState extends State<QuotedTextNodeWidget>
|
||||
key: _richTextKey,
|
||||
placeholderText: 'Quote',
|
||||
textNode: widget.textNode,
|
||||
lineHeight: widget.editorState.editorStyle.textStyle.lineHeight,
|
||||
editorState: widget.editorState,
|
||||
),
|
||||
),
|
||||
|
@ -1,8 +1,8 @@
|
||||
import 'package:appflowy_editor/src/document/node.dart';
|
||||
import 'package:appflowy_editor/src/editor_state.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/built_in_text_widget.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/default_selectable.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/flowy_rich_text.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||
import 'package:appflowy_editor/src/render/selection/selectable.dart';
|
||||
import 'package:appflowy_editor/src/service/render_plugin_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -23,14 +23,16 @@ class RichTextNodeWidgetBuilder extends NodeWidgetBuilder<TextNode> {
|
||||
});
|
||||
}
|
||||
|
||||
class RichTextNodeWidget extends StatefulWidget {
|
||||
class RichTextNodeWidget extends BuiltInTextWidget {
|
||||
const RichTextNodeWidget({
|
||||
Key? key,
|
||||
required this.textNode,
|
||||
required this.editorState,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
final TextNode textNode;
|
||||
@override
|
||||
final EditorState editorState;
|
||||
|
||||
@override
|
||||
@ -40,7 +42,7 @@ class RichTextNodeWidget extends StatefulWidget {
|
||||
// customize
|
||||
|
||||
class _RichTextNodeWidgetState extends State<RichTextNodeWidget>
|
||||
with SelectableMixin, DefaultSelectable {
|
||||
with SelectableMixin, DefaultSelectable, BuiltInStyleMixin {
|
||||
@override
|
||||
GlobalKey? get iconKey => null;
|
||||
|
||||
@ -50,13 +52,19 @@ class _RichTextNodeWidgetState extends State<RichTextNodeWidget>
|
||||
SelectableMixin<StatefulWidget> get forward =>
|
||||
_richTextKey.currentState as SelectableMixin;
|
||||
|
||||
@override
|
||||
Offset get baseOffset {
|
||||
return padding.topLeft;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: defaultLinePadding),
|
||||
padding: padding,
|
||||
child: FlowyRichText(
|
||||
key: _richTextKey,
|
||||
textNode: widget.textNode,
|
||||
lineHeight: widget.editorState.editorStyle.textStyle.lineHeight,
|
||||
editorState: widget.editorState,
|
||||
),
|
||||
);
|
||||
|
@ -1,282 +0,0 @@
|
||||
import 'package:appflowy_editor/src/document/attributes.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
///
|
||||
/// Supported partial rendering types:
|
||||
/// bold, italic,
|
||||
/// underline, strikethrough,
|
||||
/// color, font,
|
||||
/// href
|
||||
///
|
||||
/// Supported global rendering types:
|
||||
/// heading: h1, h2, h3, h4, h5, h6, ...
|
||||
/// block quote,
|
||||
/// list: ordered list, bulleted list,
|
||||
/// code block
|
||||
///
|
||||
class StyleKey {
|
||||
static String bold = 'bold';
|
||||
static String italic = 'italic';
|
||||
static String underline = 'underline';
|
||||
static String strikethrough = 'strikethrough';
|
||||
static String color = 'color';
|
||||
static String backgroundColor = 'backgroundColor';
|
||||
static String font = 'font';
|
||||
static String href = 'href';
|
||||
|
||||
static String subtype = 'subtype';
|
||||
static String heading = 'heading';
|
||||
static String h1 = 'h1';
|
||||
static String h2 = 'h2';
|
||||
static String h3 = 'h3';
|
||||
static String h4 = 'h4';
|
||||
static String h5 = 'h5';
|
||||
static String h6 = 'h6';
|
||||
|
||||
static String bulletedList = 'bulleted-list';
|
||||
static String numberList = 'number-list';
|
||||
|
||||
static String quote = 'quote';
|
||||
static String checkbox = 'checkbox';
|
||||
static String code = 'code';
|
||||
static String number = 'number';
|
||||
|
||||
static List<String> partialStyleKeys = [
|
||||
StyleKey.bold,
|
||||
StyleKey.italic,
|
||||
StyleKey.underline,
|
||||
StyleKey.strikethrough,
|
||||
StyleKey.backgroundColor,
|
||||
StyleKey.href,
|
||||
StyleKey.code,
|
||||
];
|
||||
|
||||
static List<String> globalStyleKeys = [
|
||||
StyleKey.subtype,
|
||||
StyleKey.heading,
|
||||
StyleKey.checkbox,
|
||||
StyleKey.bulletedList,
|
||||
StyleKey.numberList,
|
||||
StyleKey.quote,
|
||||
];
|
||||
}
|
||||
|
||||
// TODO: customize
|
||||
double defaultLinePadding = 8.0;
|
||||
double baseFontSize = 16.0;
|
||||
String defaultHighlightColor = '0x6000BCF0';
|
||||
String defaultBackgroundColor = '0x00000000';
|
||||
// TODO: customize.
|
||||
Map<String, double> headingToFontSize = {
|
||||
StyleKey.h1: baseFontSize + 15,
|
||||
StyleKey.h2: baseFontSize + 12,
|
||||
StyleKey.h3: baseFontSize + 9,
|
||||
StyleKey.h4: baseFontSize + 6,
|
||||
StyleKey.h5: baseFontSize + 3,
|
||||
StyleKey.h6: baseFontSize,
|
||||
};
|
||||
|
||||
extension NodeAttributesExtensions on Attributes {
|
||||
String? get heading {
|
||||
if (containsKey(StyleKey.subtype) &&
|
||||
containsKey(StyleKey.heading) &&
|
||||
this[StyleKey.subtype] == StyleKey.heading &&
|
||||
this[StyleKey.heading] is String) {
|
||||
return this[StyleKey.heading];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
double get fontSize {
|
||||
if (heading != null) {
|
||||
return headingToFontSize[heading]!;
|
||||
}
|
||||
return baseFontSize;
|
||||
}
|
||||
|
||||
bool get quote {
|
||||
return containsKey(StyleKey.quote);
|
||||
}
|
||||
|
||||
Color? get quoteColor {
|
||||
if (quote) {
|
||||
return Colors.grey;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
int? get number {
|
||||
if (containsKey(StyleKey.number) && this[StyleKey.number] is int) {
|
||||
return this[StyleKey.number];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
bool get code {
|
||||
if (containsKey(StyleKey.code) && this[StyleKey.code] == true) {
|
||||
return this[StyleKey.code];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool get check {
|
||||
if (containsKey(StyleKey.checkbox) && this[StyleKey.checkbox] is bool) {
|
||||
return this[StyleKey.checkbox];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
extension DeltaAttributesExtensions on Attributes {
|
||||
bool get bold {
|
||||
return (containsKey(StyleKey.bold) && this[StyleKey.bold] == true);
|
||||
}
|
||||
|
||||
bool get italic {
|
||||
return (containsKey(StyleKey.italic) && this[StyleKey.italic] == true);
|
||||
}
|
||||
|
||||
bool get underline {
|
||||
return (containsKey(StyleKey.underline) &&
|
||||
this[StyleKey.underline] == true);
|
||||
}
|
||||
|
||||
bool get strikethrough {
|
||||
return (containsKey(StyleKey.strikethrough) &&
|
||||
this[StyleKey.strikethrough] == true);
|
||||
}
|
||||
|
||||
Color? get color {
|
||||
if (containsKey(StyleKey.color) && this[StyleKey.color] is String) {
|
||||
return Color(
|
||||
int.parse(this[StyleKey.color]),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Color? get backgroundColor {
|
||||
if (containsKey(StyleKey.backgroundColor) &&
|
||||
this[StyleKey.backgroundColor] is String) {
|
||||
return Color(
|
||||
int.parse(this[StyleKey.backgroundColor]),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String? get font {
|
||||
// TODO: unspport now.
|
||||
return null;
|
||||
}
|
||||
|
||||
String? get href {
|
||||
if (containsKey(StyleKey.href) && this[StyleKey.href] is String) {
|
||||
return this[StyleKey.href];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class RichTextStyle {
|
||||
// TODO: customize
|
||||
RichTextStyle({
|
||||
required this.attributes,
|
||||
required this.text,
|
||||
this.gestureRecognizer,
|
||||
this.height = 1.5,
|
||||
});
|
||||
|
||||
final Attributes attributes;
|
||||
final String text;
|
||||
final GestureRecognizer? gestureRecognizer;
|
||||
final double height;
|
||||
|
||||
TextSpan toTextSpan() => _toTextSpan(height);
|
||||
|
||||
double get topPadding {
|
||||
return 0;
|
||||
}
|
||||
|
||||
TextSpan _toTextSpan(double? height) {
|
||||
return TextSpan(
|
||||
text: text,
|
||||
recognizer: _recognizer,
|
||||
style: TextStyle(
|
||||
fontWeight: _fontWeight,
|
||||
fontStyle: _fontStyle,
|
||||
fontSize: _fontSize,
|
||||
color: _textColor,
|
||||
decoration: _textDecoration,
|
||||
background: _background,
|
||||
height: height,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Paint? get _background {
|
||||
if (_backgroundColor != null) {
|
||||
return Paint()
|
||||
..color = _backgroundColor!
|
||||
..strokeWidth = 24.0
|
||||
..style = PaintingStyle.fill
|
||||
..strokeJoin = StrokeJoin.round;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// bold
|
||||
FontWeight get _fontWeight {
|
||||
if (attributes.bold) {
|
||||
return FontWeight.bold;
|
||||
}
|
||||
return FontWeight.normal;
|
||||
}
|
||||
|
||||
// underline or strikethrough
|
||||
TextDecoration get _textDecoration {
|
||||
var decorations = [TextDecoration.none];
|
||||
if (attributes.underline || attributes.href != null) {
|
||||
decorations.add(TextDecoration.underline);
|
||||
}
|
||||
if (attributes.strikethrough) {
|
||||
decorations.add(TextDecoration.lineThrough);
|
||||
}
|
||||
return TextDecoration.combine(decorations);
|
||||
}
|
||||
|
||||
// font
|
||||
FontStyle get _fontStyle =>
|
||||
attributes.italic ? FontStyle.italic : FontStyle.normal;
|
||||
|
||||
// text color
|
||||
Color get _textColor {
|
||||
if (attributes.href != null) {
|
||||
return Colors.lightBlue;
|
||||
}
|
||||
if (attributes.code) {
|
||||
return Colors.lightBlue.withOpacity(0.8);
|
||||
}
|
||||
return attributes.color ?? Colors.black;
|
||||
}
|
||||
|
||||
Color? get _backgroundColor {
|
||||
if (attributes.backgroundColor != null) {
|
||||
return attributes.backgroundColor!;
|
||||
} else if (attributes.code) {
|
||||
return Colors.blue.shade300.withOpacity(0.3);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// font size
|
||||
double get _fontSize {
|
||||
return baseFontSize;
|
||||
}
|
||||
|
||||
// recognizer
|
||||
GestureRecognizer? get _recognizer {
|
||||
return gestureRecognizer;
|
||||
}
|
||||
}
|
@ -1,10 +1,12 @@
|
||||
import 'package:appflowy_editor/src/editor_state.dart';
|
||||
import 'package:appflowy_editor/src/infra/flowy_svg.dart';
|
||||
import 'package:appflowy_editor/src/l10n/l10n.dart';
|
||||
import 'package:appflowy_editor/src/render/image/image_upload_widget.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||
import 'package:appflowy_editor/src/render/selection_menu/selection_menu_widget.dart';
|
||||
import 'package:appflowy_editor/src/service/default_text_operations/format_rich_text_style.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
||||
|
||||
abstract class SelectionMenuService {
|
||||
Offset get topLeft;
|
||||
@ -54,7 +56,13 @@ class SelectionMenu implements SelectionMenuService {
|
||||
if (selectionRects.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final offset = selectionRects.first.bottomRight + const Offset(10, 10);
|
||||
// Workaround: We can customize the padding through the [EditorStyle],
|
||||
// but the coordinates of overlay are not properly converted currently.
|
||||
// Just subtract the padding here as a result.
|
||||
final baseOffset =
|
||||
editorState.renderBox?.localToGlobal(Offset.zero) ?? Offset.zero;
|
||||
final offset =
|
||||
selectionRects.first.bottomRight + const Offset(10, 10) - baseOffset;
|
||||
_topLeft = offset;
|
||||
|
||||
_selectionMenuEntry = OverlayEntry(builder: (context) {
|
||||
@ -116,7 +124,7 @@ List<SelectionMenuItem> get defaultSelectionMenuItems =>
|
||||
_defaultSelectionMenuItems;
|
||||
final List<SelectionMenuItem> _defaultSelectionMenuItems = [
|
||||
SelectionMenuItem(
|
||||
name: 'Text',
|
||||
name: AppFlowyEditorLocalizations.current.text,
|
||||
icon: _selectionMenuIcon('text'),
|
||||
keywords: ['text'],
|
||||
handler: (editorState, _, __) {
|
||||
@ -124,37 +132,37 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
|
||||
},
|
||||
),
|
||||
SelectionMenuItem(
|
||||
name: 'Heading 1',
|
||||
name: AppFlowyEditorLocalizations.current.heading1,
|
||||
icon: _selectionMenuIcon('h1'),
|
||||
keywords: ['heading 1, h1'],
|
||||
handler: (editorState, _, __) {
|
||||
insertHeadingAfterSelection(editorState, StyleKey.h1);
|
||||
insertHeadingAfterSelection(editorState, BuiltInAttributeKey.h1);
|
||||
},
|
||||
),
|
||||
SelectionMenuItem(
|
||||
name: 'Heading 2',
|
||||
name: AppFlowyEditorLocalizations.current.heading2,
|
||||
icon: _selectionMenuIcon('h2'),
|
||||
keywords: ['heading 2, h2'],
|
||||
handler: (editorState, _, __) {
|
||||
insertHeadingAfterSelection(editorState, StyleKey.h2);
|
||||
insertHeadingAfterSelection(editorState, BuiltInAttributeKey.h2);
|
||||
},
|
||||
),
|
||||
SelectionMenuItem(
|
||||
name: 'Heading 3',
|
||||
name: AppFlowyEditorLocalizations.current.heading3,
|
||||
icon: _selectionMenuIcon('h3'),
|
||||
keywords: ['heading 3, h3'],
|
||||
handler: (editorState, _, __) {
|
||||
insertHeadingAfterSelection(editorState, StyleKey.h3);
|
||||
insertHeadingAfterSelection(editorState, BuiltInAttributeKey.h3);
|
||||
},
|
||||
),
|
||||
SelectionMenuItem(
|
||||
name: 'Image',
|
||||
name: AppFlowyEditorLocalizations.current.image,
|
||||
icon: _selectionMenuIcon('image'),
|
||||
keywords: ['image'],
|
||||
handler: showImageUploadMenu,
|
||||
),
|
||||
SelectionMenuItem(
|
||||
name: 'Bulleted list',
|
||||
name: AppFlowyEditorLocalizations.current.bulletedList,
|
||||
icon: _selectionMenuIcon('bulleted_list'),
|
||||
keywords: ['bulleted list', 'list', 'unordered list'],
|
||||
handler: (editorState, _, __) {
|
||||
@ -162,7 +170,7 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
|
||||
},
|
||||
),
|
||||
SelectionMenuItem(
|
||||
name: 'Checkbox',
|
||||
name: AppFlowyEditorLocalizations.current.checkbox,
|
||||
icon: _selectionMenuIcon('checkbox'),
|
||||
keywords: ['todo list', 'list', 'checkbox list'],
|
||||
handler: (editorState, _, __) {
|
||||
@ -170,7 +178,7 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
|
||||
},
|
||||
),
|
||||
SelectionMenuItem(
|
||||
name: 'Quote',
|
||||
name: AppFlowyEditorLocalizations.current.quote,
|
||||
icon: _selectionMenuIcon('quote'),
|
||||
keywords: ['quote', 'refer'],
|
||||
handler: (editorState, _, __) {
|
||||
|
@ -1,20 +1,254 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy_editor/src/document/node.dart';
|
||||
import 'package:appflowy_editor/src/editor_state.dart';
|
||||
import 'package:appflowy_editor/src/extensions/attributes_extension.dart';
|
||||
|
||||
typedef PluginStyler = Object Function(EditorState editorState, Node node);
|
||||
typedef PluginStyle = Map<String, PluginStyler>;
|
||||
|
||||
/// Editor style configuration
|
||||
class EditorStyle {
|
||||
const EditorStyle({
|
||||
EditorStyle({
|
||||
required this.padding,
|
||||
});
|
||||
required this.textStyle,
|
||||
required this.cursorColor,
|
||||
required this.selectionColor,
|
||||
Map<String, PluginStyle> pluginStyles = const {},
|
||||
}) {
|
||||
_pluginStyles.addAll(pluginStyles);
|
||||
}
|
||||
|
||||
const EditorStyle.defaultStyle()
|
||||
: padding = const EdgeInsets.fromLTRB(200.0, 0.0, 200.0, 0.0);
|
||||
EditorStyle.defaultStyle()
|
||||
: padding = const EdgeInsets.fromLTRB(200.0, 0.0, 200.0, 0.0),
|
||||
textStyle = BuiltInTextStyle.builtIn(),
|
||||
cursorColor = const Color(0xFF00BCF0),
|
||||
selectionColor = const Color.fromARGB(53, 111, 201, 231);
|
||||
|
||||
/// The margin of the document context from the editor.
|
||||
final EdgeInsets padding;
|
||||
final BuiltInTextStyle textStyle;
|
||||
final Color cursorColor;
|
||||
final Color selectionColor;
|
||||
|
||||
EditorStyle copyWith({EdgeInsets? padding}) {
|
||||
final Map<String, PluginStyle> _pluginStyles = Map.from(builtInTextStylers);
|
||||
|
||||
Object? style(EditorState editorState, Node node, String key) {
|
||||
final styler = _pluginStyles[node.id]?[key];
|
||||
if (styler != null) {
|
||||
return styler(editorState, node);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
EditorStyle copyWith({
|
||||
EdgeInsets? padding,
|
||||
BuiltInTextStyle? textStyle,
|
||||
Color? cursorColor,
|
||||
Color? selectionColor,
|
||||
Map<String, PluginStyle>? pluginStyles,
|
||||
}) {
|
||||
return EditorStyle(
|
||||
padding: padding ?? this.padding,
|
||||
textStyle: textStyle ?? this.textStyle,
|
||||
cursorColor: cursorColor ?? this.cursorColor,
|
||||
selectionColor: selectionColor ?? this.selectionColor,
|
||||
pluginStyles: pluginStyles ?? {},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is EditorStyle &&
|
||||
other.padding == padding &&
|
||||
other.textStyle == textStyle &&
|
||||
other.cursorColor == cursorColor &&
|
||||
other.selectionColor == selectionColor;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return padding.hashCode ^
|
||||
textStyle.hashCode ^
|
||||
cursorColor.hashCode ^
|
||||
selectionColor.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
PluginStyle get builtInPluginStyle => Map.from({
|
||||
'padding': (_, __) => const EdgeInsets.symmetric(vertical: 8.0),
|
||||
'textStyle': (_, __) => const TextStyle(),
|
||||
'iconSize': (_, __) => const Size.square(20.0),
|
||||
'iconPadding': (_, __) => const EdgeInsets.only(right: 5.0),
|
||||
});
|
||||
|
||||
Map<String, PluginStyle> builtInTextStylers = {
|
||||
'text': builtInPluginStyle,
|
||||
'text/checkbox': builtInPluginStyle
|
||||
..update(
|
||||
'textStyle',
|
||||
(_) => (EditorState editorState, Node node) {
|
||||
if (node is TextNode && node.attributes.check == true) {
|
||||
return const TextStyle(
|
||||
color: Colors.grey,
|
||||
decoration: TextDecoration.lineThrough,
|
||||
);
|
||||
}
|
||||
return const TextStyle();
|
||||
},
|
||||
),
|
||||
'text/heading': builtInPluginStyle
|
||||
..update(
|
||||
'textStyle',
|
||||
(_) => (EditorState editorState, Node node) {
|
||||
final headingToFontSize = {
|
||||
'h1': 32.0,
|
||||
'h2': 28.0,
|
||||
'h3': 24.0,
|
||||
'h4': 18.0,
|
||||
'h5': 18.0,
|
||||
'h6': 18.0,
|
||||
};
|
||||
final fontSize = headingToFontSize[node.attributes.heading] ?? 18.0;
|
||||
return TextStyle(fontSize: fontSize, fontWeight: FontWeight.bold);
|
||||
},
|
||||
),
|
||||
'text/bulleted-list': builtInPluginStyle,
|
||||
'text/number-list': builtInPluginStyle
|
||||
..update(
|
||||
'iconPadding',
|
||||
(_) => (EditorState editorState, Node node) {
|
||||
return const EdgeInsets.only(left: 5.0, right: 5.0);
|
||||
},
|
||||
),
|
||||
'text/quote': builtInPluginStyle,
|
||||
'image': builtInPluginStyle,
|
||||
};
|
||||
|
||||
class BuiltInTextStyle {
|
||||
const BuiltInTextStyle({
|
||||
required this.defaultTextStyle,
|
||||
required this.defaultPlaceholderTextStyle,
|
||||
required this.bold,
|
||||
required this.italic,
|
||||
required this.underline,
|
||||
required this.strikethrough,
|
||||
required this.href,
|
||||
required this.code,
|
||||
this.highlightColorHex = '0x6000BCF0',
|
||||
this.lineHeight = 1.5,
|
||||
});
|
||||
|
||||
final TextStyle defaultTextStyle;
|
||||
final TextStyle defaultPlaceholderTextStyle;
|
||||
final TextStyle bold;
|
||||
final TextStyle italic;
|
||||
final TextStyle underline;
|
||||
final TextStyle strikethrough;
|
||||
final TextStyle href;
|
||||
final TextStyle code;
|
||||
final String highlightColorHex;
|
||||
final double lineHeight;
|
||||
|
||||
BuiltInTextStyle.builtIn()
|
||||
: defaultTextStyle = const TextStyle(fontSize: 16.0, color: Colors.black),
|
||||
defaultPlaceholderTextStyle =
|
||||
const TextStyle(fontSize: 16.0, color: Colors.grey),
|
||||
bold = const TextStyle(fontWeight: FontWeight.bold),
|
||||
italic = const TextStyle(fontStyle: FontStyle.italic),
|
||||
underline = const TextStyle(decoration: TextDecoration.underline),
|
||||
strikethrough = const TextStyle(decoration: TextDecoration.lineThrough),
|
||||
href = const TextStyle(
|
||||
color: Colors.blue,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
code = const TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
color: Color(0xFF00BCF0),
|
||||
backgroundColor: Color(0xFFE0F8FF),
|
||||
),
|
||||
highlightColorHex = '0x6000BCF0',
|
||||
lineHeight = 1.5;
|
||||
|
||||
BuiltInTextStyle.builtInDarkMode()
|
||||
: defaultTextStyle = const TextStyle(fontSize: 16.0, color: Colors.white),
|
||||
defaultPlaceholderTextStyle = TextStyle(
|
||||
fontSize: 16.0,
|
||||
color: Colors.white.withOpacity(0.3),
|
||||
),
|
||||
bold = const TextStyle(fontWeight: FontWeight.bold),
|
||||
italic = const TextStyle(fontStyle: FontStyle.italic),
|
||||
underline = const TextStyle(decoration: TextDecoration.underline),
|
||||
strikethrough = const TextStyle(decoration: TextDecoration.lineThrough),
|
||||
href = const TextStyle(
|
||||
color: Colors.blue,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
code = const TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
color: Color(0xFF00BCF0),
|
||||
backgroundColor: Color(0xFFE0F8FF),
|
||||
),
|
||||
highlightColorHex = '0x6000BCF0',
|
||||
lineHeight = 1.5;
|
||||
|
||||
BuiltInTextStyle copyWith({
|
||||
TextStyle? defaultTextStyle,
|
||||
TextStyle? defaultPlaceholderTextStyle,
|
||||
TextStyle? bold,
|
||||
TextStyle? italic,
|
||||
TextStyle? underline,
|
||||
TextStyle? strikethrough,
|
||||
TextStyle? href,
|
||||
TextStyle? code,
|
||||
String? highlightColorHex,
|
||||
double? lineHeight,
|
||||
}) {
|
||||
return BuiltInTextStyle(
|
||||
defaultTextStyle: defaultTextStyle ?? this.defaultTextStyle,
|
||||
defaultPlaceholderTextStyle:
|
||||
defaultPlaceholderTextStyle ?? this.defaultPlaceholderTextStyle,
|
||||
bold: bold ?? this.bold,
|
||||
italic: italic ?? this.italic,
|
||||
underline: underline ?? this.underline,
|
||||
strikethrough: strikethrough ?? this.strikethrough,
|
||||
href: href ?? this.href,
|
||||
code: code ?? this.code,
|
||||
highlightColorHex: highlightColorHex ?? this.highlightColorHex,
|
||||
lineHeight: lineHeight ?? this.lineHeight,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is BuiltInTextStyle &&
|
||||
other.defaultTextStyle == defaultTextStyle &&
|
||||
other.defaultPlaceholderTextStyle == defaultPlaceholderTextStyle &&
|
||||
other.bold == bold &&
|
||||
other.italic == italic &&
|
||||
other.underline == underline &&
|
||||
other.strikethrough == strikethrough &&
|
||||
other.href == href &&
|
||||
other.code == code &&
|
||||
other.highlightColorHex == highlightColorHex &&
|
||||
other.lineHeight == lineHeight;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return defaultTextStyle.hashCode ^
|
||||
defaultPlaceholderTextStyle.hashCode ^
|
||||
bold.hashCode ^
|
||||
italic.hashCode ^
|
||||
underline.hashCode ^
|
||||
strikethrough.hashCode ^
|
||||
href.hashCode ^
|
||||
code.hashCode ^
|
||||
highlightColorHex.hashCode ^
|
||||
lineHeight.hashCode;
|
||||
}
|
||||
}
|
||||
|
@ -2,12 +2,13 @@ import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/extensions/url_launcher_extension.dart';
|
||||
import 'package:appflowy_editor/src/infra/flowy_svg.dart';
|
||||
import 'package:appflowy_editor/src/render/link_menu/link_menu.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||
import 'package:appflowy_editor/src/extensions/text_node_extensions.dart';
|
||||
import 'package:appflowy_editor/src/extensions/editor_state_extensions.dart';
|
||||
import 'package:appflowy_editor/src/service/default_text_operations/format_rich_text_style.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rich_clipboard/rich_clipboard.dart';
|
||||
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
||||
|
||||
typedef ToolbarItemEventHandler = void Function(
|
||||
EditorState editorState, BuildContext context);
|
||||
@ -63,7 +64,7 @@ List<ToolbarItem> defaultToolbarItems = [
|
||||
ToolbarItem(
|
||||
id: 'appflowy.toolbar.h1',
|
||||
type: 1,
|
||||
tooltipsMessage: 'Heading 1',
|
||||
tooltipsMessage: AppFlowyEditorLocalizations.current.heading1,
|
||||
iconBuilder: (isHighlight) => FlowySvg(
|
||||
name: 'toolbar/h1',
|
||||
color: isHighlight ? Colors.lightBlue : null,
|
||||
@ -71,15 +72,16 @@ List<ToolbarItem> defaultToolbarItems = [
|
||||
validator: _onlyShowInSingleTextSelection,
|
||||
highlightCallback: (editorState) => _allSatisfy(
|
||||
editorState,
|
||||
StyleKey.heading,
|
||||
(value) => value == StyleKey.h1,
|
||||
BuiltInAttributeKey.heading,
|
||||
(value) => value == BuiltInAttributeKey.h1,
|
||||
),
|
||||
handler: (editorState, context) => formatHeading(editorState, StyleKey.h1),
|
||||
handler: (editorState, context) =>
|
||||
formatHeading(editorState, BuiltInAttributeKey.h1),
|
||||
),
|
||||
ToolbarItem(
|
||||
id: 'appflowy.toolbar.h2',
|
||||
type: 1,
|
||||
tooltipsMessage: 'Heading 2',
|
||||
tooltipsMessage: AppFlowyEditorLocalizations.current.heading2,
|
||||
iconBuilder: (isHighlight) => FlowySvg(
|
||||
name: 'toolbar/h2',
|
||||
color: isHighlight ? Colors.lightBlue : null,
|
||||
@ -87,15 +89,16 @@ List<ToolbarItem> defaultToolbarItems = [
|
||||
validator: _onlyShowInSingleTextSelection,
|
||||
highlightCallback: (editorState) => _allSatisfy(
|
||||
editorState,
|
||||
StyleKey.heading,
|
||||
(value) => value == StyleKey.h2,
|
||||
BuiltInAttributeKey.heading,
|
||||
(value) => value == BuiltInAttributeKey.h2,
|
||||
),
|
||||
handler: (editorState, context) => formatHeading(editorState, StyleKey.h2),
|
||||
handler: (editorState, context) =>
|
||||
formatHeading(editorState, BuiltInAttributeKey.h2),
|
||||
),
|
||||
ToolbarItem(
|
||||
id: 'appflowy.toolbar.h3',
|
||||
type: 1,
|
||||
tooltipsMessage: 'Heading 3',
|
||||
tooltipsMessage: AppFlowyEditorLocalizations.current.heading3,
|
||||
iconBuilder: (isHighlight) => FlowySvg(
|
||||
name: 'toolbar/h3',
|
||||
color: isHighlight ? Colors.lightBlue : null,
|
||||
@ -103,15 +106,16 @@ List<ToolbarItem> defaultToolbarItems = [
|
||||
validator: _onlyShowInSingleTextSelection,
|
||||
highlightCallback: (editorState) => _allSatisfy(
|
||||
editorState,
|
||||
StyleKey.heading,
|
||||
(value) => value == StyleKey.h3,
|
||||
BuiltInAttributeKey.heading,
|
||||
(value) => value == BuiltInAttributeKey.h3,
|
||||
),
|
||||
handler: (editorState, context) => formatHeading(editorState, StyleKey.h3),
|
||||
handler: (editorState, context) =>
|
||||
formatHeading(editorState, BuiltInAttributeKey.h3),
|
||||
),
|
||||
ToolbarItem(
|
||||
id: 'appflowy.toolbar.bold',
|
||||
type: 2,
|
||||
tooltipsMessage: 'Bold',
|
||||
tooltipsMessage: AppFlowyEditorLocalizations.current.bold,
|
||||
iconBuilder: (isHighlight) => FlowySvg(
|
||||
name: 'toolbar/bold',
|
||||
color: isHighlight ? Colors.lightBlue : null,
|
||||
@ -119,7 +123,7 @@ List<ToolbarItem> defaultToolbarItems = [
|
||||
validator: _showInTextSelection,
|
||||
highlightCallback: (editorState) => _allSatisfy(
|
||||
editorState,
|
||||
StyleKey.bold,
|
||||
BuiltInAttributeKey.bold,
|
||||
(value) => value == true,
|
||||
),
|
||||
handler: (editorState, context) => formatBold(editorState),
|
||||
@ -127,7 +131,7 @@ List<ToolbarItem> defaultToolbarItems = [
|
||||
ToolbarItem(
|
||||
id: 'appflowy.toolbar.italic',
|
||||
type: 2,
|
||||
tooltipsMessage: 'Italic',
|
||||
tooltipsMessage: AppFlowyEditorLocalizations.current.italic,
|
||||
iconBuilder: (isHighlight) => FlowySvg(
|
||||
name: 'toolbar/italic',
|
||||
color: isHighlight ? Colors.lightBlue : null,
|
||||
@ -135,7 +139,7 @@ List<ToolbarItem> defaultToolbarItems = [
|
||||
validator: _showInTextSelection,
|
||||
highlightCallback: (editorState) => _allSatisfy(
|
||||
editorState,
|
||||
StyleKey.italic,
|
||||
BuiltInAttributeKey.italic,
|
||||
(value) => value == true,
|
||||
),
|
||||
handler: (editorState, context) => formatItalic(editorState),
|
||||
@ -143,7 +147,7 @@ List<ToolbarItem> defaultToolbarItems = [
|
||||
ToolbarItem(
|
||||
id: 'appflowy.toolbar.underline',
|
||||
type: 2,
|
||||
tooltipsMessage: 'Underline',
|
||||
tooltipsMessage: AppFlowyEditorLocalizations.current.underline,
|
||||
iconBuilder: (isHighlight) => FlowySvg(
|
||||
name: 'toolbar/underline',
|
||||
color: isHighlight ? Colors.lightBlue : null,
|
||||
@ -151,7 +155,7 @@ List<ToolbarItem> defaultToolbarItems = [
|
||||
validator: _showInTextSelection,
|
||||
highlightCallback: (editorState) => _allSatisfy(
|
||||
editorState,
|
||||
StyleKey.underline,
|
||||
BuiltInAttributeKey.underline,
|
||||
(value) => value == true,
|
||||
),
|
||||
handler: (editorState, context) => formatUnderline(editorState),
|
||||
@ -159,7 +163,7 @@ List<ToolbarItem> defaultToolbarItems = [
|
||||
ToolbarItem(
|
||||
id: 'appflowy.toolbar.strikethrough',
|
||||
type: 2,
|
||||
tooltipsMessage: 'Strikethrough',
|
||||
tooltipsMessage: AppFlowyEditorLocalizations.current.strikethrough,
|
||||
iconBuilder: (isHighlight) => FlowySvg(
|
||||
name: 'toolbar/strikethrough',
|
||||
color: isHighlight ? Colors.lightBlue : null,
|
||||
@ -167,7 +171,7 @@ List<ToolbarItem> defaultToolbarItems = [
|
||||
validator: _showInTextSelection,
|
||||
highlightCallback: (editorState) => _allSatisfy(
|
||||
editorState,
|
||||
StyleKey.strikethrough,
|
||||
BuiltInAttributeKey.strikethrough,
|
||||
(value) => value == true,
|
||||
),
|
||||
handler: (editorState, context) => formatStrikethrough(editorState),
|
||||
@ -175,7 +179,7 @@ List<ToolbarItem> defaultToolbarItems = [
|
||||
ToolbarItem(
|
||||
id: 'appflowy.toolbar.code',
|
||||
type: 2,
|
||||
tooltipsMessage: 'Embed Code',
|
||||
tooltipsMessage: AppFlowyEditorLocalizations.current.embedCode,
|
||||
iconBuilder: (isHighlight) => FlowySvg(
|
||||
name: 'toolbar/code',
|
||||
color: isHighlight ? Colors.lightBlue : null,
|
||||
@ -183,15 +187,15 @@ List<ToolbarItem> defaultToolbarItems = [
|
||||
validator: _showInTextSelection,
|
||||
highlightCallback: (editorState) => _allSatisfy(
|
||||
editorState,
|
||||
StyleKey.code,
|
||||
(value) => value == StyleKey.code,
|
||||
BuiltInAttributeKey.code,
|
||||
(value) => value == true,
|
||||
),
|
||||
handler: (editorState, context) => formatEmbedCode(editorState),
|
||||
),
|
||||
ToolbarItem(
|
||||
id: 'appflowy.toolbar.quote',
|
||||
type: 3,
|
||||
tooltipsMessage: 'Quote',
|
||||
tooltipsMessage: AppFlowyEditorLocalizations.current.quote,
|
||||
iconBuilder: (isHighlight) => FlowySvg(
|
||||
name: 'toolbar/quote',
|
||||
color: isHighlight ? Colors.lightBlue : null,
|
||||
@ -199,15 +203,15 @@ List<ToolbarItem> defaultToolbarItems = [
|
||||
validator: _onlyShowInSingleTextSelection,
|
||||
highlightCallback: (editorState) => _allSatisfy(
|
||||
editorState,
|
||||
StyleKey.subtype,
|
||||
(value) => value == StyleKey.quote,
|
||||
BuiltInAttributeKey.subtype,
|
||||
(value) => value == BuiltInAttributeKey.quote,
|
||||
),
|
||||
handler: (editorState, context) => formatQuote(editorState),
|
||||
),
|
||||
ToolbarItem(
|
||||
id: 'appflowy.toolbar.bulleted_list',
|
||||
type: 3,
|
||||
tooltipsMessage: 'Bulleted list',
|
||||
tooltipsMessage: AppFlowyEditorLocalizations.current.bulletedList,
|
||||
iconBuilder: (isHighlight) => FlowySvg(
|
||||
name: 'toolbar/bulleted_list',
|
||||
color: isHighlight ? Colors.lightBlue : null,
|
||||
@ -215,15 +219,15 @@ List<ToolbarItem> defaultToolbarItems = [
|
||||
validator: _onlyShowInSingleTextSelection,
|
||||
highlightCallback: (editorState) => _allSatisfy(
|
||||
editorState,
|
||||
StyleKey.subtype,
|
||||
(value) => value == StyleKey.bulletedList,
|
||||
BuiltInAttributeKey.subtype,
|
||||
(value) => value == BuiltInAttributeKey.bulletedList,
|
||||
),
|
||||
handler: (editorState, context) => formatBulletedList(editorState),
|
||||
),
|
||||
ToolbarItem(
|
||||
id: 'appflowy.toolbar.link',
|
||||
type: 4,
|
||||
tooltipsMessage: 'Link',
|
||||
tooltipsMessage: AppFlowyEditorLocalizations.current.link,
|
||||
iconBuilder: (isHighlight) => FlowySvg(
|
||||
name: 'toolbar/link',
|
||||
color: isHighlight ? Colors.lightBlue : null,
|
||||
@ -231,7 +235,7 @@ List<ToolbarItem> defaultToolbarItems = [
|
||||
validator: _onlyShowInSingleTextSelection,
|
||||
highlightCallback: (editorState) => _allSatisfy(
|
||||
editorState,
|
||||
StyleKey.href,
|
||||
BuiltInAttributeKey.href,
|
||||
(value) => value != null,
|
||||
),
|
||||
handler: (editorState, context) => showLinkMenu(context, editorState),
|
||||
@ -239,7 +243,7 @@ List<ToolbarItem> defaultToolbarItems = [
|
||||
ToolbarItem(
|
||||
id: 'appflowy.toolbar.highlight',
|
||||
type: 4,
|
||||
tooltipsMessage: 'Highlight',
|
||||
tooltipsMessage: AppFlowyEditorLocalizations.current.highlight,
|
||||
iconBuilder: (isHighlight) => FlowySvg(
|
||||
name: 'toolbar/highlight',
|
||||
color: isHighlight ? Colors.lightBlue : null,
|
||||
@ -247,10 +251,13 @@ List<ToolbarItem> defaultToolbarItems = [
|
||||
validator: _showInTextSelection,
|
||||
highlightCallback: (editorState) => _allSatisfy(
|
||||
editorState,
|
||||
StyleKey.backgroundColor,
|
||||
BuiltInAttributeKey.backgroundColor,
|
||||
(value) => value != null,
|
||||
),
|
||||
handler: (editorState, context) => formatHighlight(editorState),
|
||||
handler: (editorState, context) => formatHighlight(
|
||||
editorState,
|
||||
editorState.editorStyle.textStyle.highlightColorHex,
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
@ -296,6 +303,9 @@ void showLinkMenu(
|
||||
matchRect = rect;
|
||||
}
|
||||
}
|
||||
final baseOffset =
|
||||
editorState.renderBox?.localToGlobal(Offset.zero) ?? Offset.zero;
|
||||
matchRect = matchRect.shift(-baseOffset);
|
||||
|
||||
_dismissLinkMenu();
|
||||
_editorState = editorState;
|
||||
@ -314,7 +324,8 @@ void showLinkMenu(
|
||||
final textNode = node.first as TextNode;
|
||||
String? linkText;
|
||||
if (textNode.allSatisfyLinkInSelection(selection)) {
|
||||
linkText = textNode.getAttributeInSelection(selection, StyleKey.href);
|
||||
linkText =
|
||||
textNode.getAttributeInSelection(selection, BuiltInAttributeKey.href);
|
||||
}
|
||||
_linkMenuOverlay = OverlayEntry(builder: (context) {
|
||||
return Positioned(
|
||||
@ -328,7 +339,8 @@ void showLinkMenu(
|
||||
},
|
||||
onSubmitted: (text) {
|
||||
TransactionBuilder(editorState)
|
||||
..formatText(textNode, index, length, {StyleKey.href: text})
|
||||
..formatText(
|
||||
textNode, index, length, {BuiltInAttributeKey.href: text})
|
||||
..commit();
|
||||
_dismissLinkMenu();
|
||||
},
|
||||
@ -338,7 +350,8 @@ void showLinkMenu(
|
||||
},
|
||||
onRemoveLink: () {
|
||||
TransactionBuilder(editorState)
|
||||
..formatText(textNode, index, length, {StyleKey.href: null})
|
||||
..formatText(
|
||||
textNode, index, length, {BuiltInAttributeKey.href: null})
|
||||
..commit();
|
||||
_dismissLinkMenu();
|
||||
},
|
||||
|
@ -6,31 +6,31 @@ import 'package:appflowy_editor/src/editor_state.dart';
|
||||
import 'package:appflowy_editor/src/extensions/text_node_extensions.dart';
|
||||
import 'package:appflowy_editor/src/extensions/path_extensions.dart';
|
||||
import 'package:appflowy_editor/src/operation/transaction_builder.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
||||
|
||||
void insertHeadingAfterSelection(EditorState editorState, String heading) {
|
||||
insertTextNodeAfterSelection(editorState, {
|
||||
StyleKey.subtype: StyleKey.heading,
|
||||
StyleKey.heading: heading,
|
||||
BuiltInAttributeKey.subtype: BuiltInAttributeKey.heading,
|
||||
BuiltInAttributeKey.heading: heading,
|
||||
});
|
||||
}
|
||||
|
||||
void insertQuoteAfterSelection(EditorState editorState) {
|
||||
insertTextNodeAfterSelection(editorState, {
|
||||
StyleKey.subtype: StyleKey.quote,
|
||||
BuiltInAttributeKey.subtype: BuiltInAttributeKey.quote,
|
||||
});
|
||||
}
|
||||
|
||||
void insertCheckboxAfterSelection(EditorState editorState) {
|
||||
insertTextNodeAfterSelection(editorState, {
|
||||
StyleKey.subtype: StyleKey.checkbox,
|
||||
StyleKey.checkbox: false,
|
||||
BuiltInAttributeKey.subtype: BuiltInAttributeKey.checkbox,
|
||||
BuiltInAttributeKey.checkbox: false,
|
||||
});
|
||||
}
|
||||
|
||||
void insertBulletedListAfterSelection(EditorState editorState) {
|
||||
insertTextNodeAfterSelection(editorState, {
|
||||
StyleKey.subtype: StyleKey.bulletedList,
|
||||
BuiltInAttributeKey.subtype: BuiltInAttributeKey.bulletedList,
|
||||
});
|
||||
}
|
||||
|
||||
@ -68,27 +68,27 @@ void formatText(EditorState editorState) {
|
||||
|
||||
void formatHeading(EditorState editorState, String heading) {
|
||||
formatTextNodes(editorState, {
|
||||
StyleKey.subtype: StyleKey.heading,
|
||||
StyleKey.heading: heading,
|
||||
BuiltInAttributeKey.subtype: BuiltInAttributeKey.heading,
|
||||
BuiltInAttributeKey.heading: heading,
|
||||
});
|
||||
}
|
||||
|
||||
void formatQuote(EditorState editorState) {
|
||||
formatTextNodes(editorState, {
|
||||
StyleKey.subtype: StyleKey.quote,
|
||||
BuiltInAttributeKey.subtype: BuiltInAttributeKey.quote,
|
||||
});
|
||||
}
|
||||
|
||||
void formatCheckbox(EditorState editorState) {
|
||||
void formatCheckbox(EditorState editorState, bool check) {
|
||||
formatTextNodes(editorState, {
|
||||
StyleKey.subtype: StyleKey.checkbox,
|
||||
StyleKey.checkbox: false,
|
||||
BuiltInAttributeKey.subtype: BuiltInAttributeKey.checkbox,
|
||||
BuiltInAttributeKey.checkbox: check,
|
||||
});
|
||||
}
|
||||
|
||||
void formatBulletedList(EditorState editorState) {
|
||||
formatTextNodes(editorState, {
|
||||
StyleKey.subtype: StyleKey.bulletedList,
|
||||
BuiltInAttributeKey.subtype: BuiltInAttributeKey.bulletedList,
|
||||
});
|
||||
}
|
||||
|
||||
@ -107,7 +107,7 @@ bool formatTextNodes(EditorState editorState, Attributes attributes) {
|
||||
..updateNode(
|
||||
textNode,
|
||||
Attributes.fromIterable(
|
||||
StyleKey.globalStyleKeys,
|
||||
BuiltInAttributeKey.globalStyleKeys,
|
||||
value: (_) => null,
|
||||
)..addAll(attributes),
|
||||
)
|
||||
@ -124,44 +124,58 @@ bool formatTextNodes(EditorState editorState, Attributes attributes) {
|
||||
}
|
||||
|
||||
bool formatBold(EditorState editorState) {
|
||||
return formatRichTextPartialStyle(editorState, StyleKey.bold);
|
||||
return formatRichTextPartialStyle(editorState, BuiltInAttributeKey.bold);
|
||||
}
|
||||
|
||||
bool formatItalic(EditorState editorState) {
|
||||
return formatRichTextPartialStyle(editorState, StyleKey.italic);
|
||||
return formatRichTextPartialStyle(editorState, BuiltInAttributeKey.italic);
|
||||
}
|
||||
|
||||
bool formatUnderline(EditorState editorState) {
|
||||
return formatRichTextPartialStyle(editorState, StyleKey.underline);
|
||||
return formatRichTextPartialStyle(editorState, BuiltInAttributeKey.underline);
|
||||
}
|
||||
|
||||
bool formatStrikethrough(EditorState editorState) {
|
||||
return formatRichTextPartialStyle(editorState, StyleKey.strikethrough);
|
||||
return formatRichTextPartialStyle(
|
||||
editorState, BuiltInAttributeKey.strikethrough);
|
||||
}
|
||||
|
||||
bool formatEmbedCode(EditorState editorState) {
|
||||
return formatRichTextPartialStyle(editorState, StyleKey.code);
|
||||
return formatRichTextPartialStyle(editorState, BuiltInAttributeKey.code);
|
||||
}
|
||||
|
||||
bool formatHighlight(EditorState editorState) {
|
||||
bool formatHighlight(EditorState editorState, String colorHex) {
|
||||
bool value = _allSatisfyInSelection(
|
||||
editorState, StyleKey.backgroundColor, defaultHighlightColor);
|
||||
return formatRichTextPartialStyle(editorState, StyleKey.backgroundColor,
|
||||
customValue: value ? defaultBackgroundColor : defaultHighlightColor);
|
||||
editorState,
|
||||
BuiltInAttributeKey.backgroundColor,
|
||||
colorHex,
|
||||
);
|
||||
return formatRichTextPartialStyle(
|
||||
editorState,
|
||||
BuiltInAttributeKey.backgroundColor,
|
||||
customValue: value ? '0x00000000' : colorHex,
|
||||
);
|
||||
}
|
||||
|
||||
bool formatRichTextPartialStyle(EditorState editorState, String styleKey,
|
||||
{Object? customValue}) {
|
||||
Attributes attributes = {
|
||||
styleKey: customValue ??
|
||||
!_allSatisfyInSelection(editorState, styleKey, customValue ?? true),
|
||||
!_allSatisfyInSelection(
|
||||
editorState,
|
||||
styleKey,
|
||||
customValue ?? true,
|
||||
),
|
||||
};
|
||||
|
||||
return formatRichTextStyle(editorState, attributes);
|
||||
}
|
||||
|
||||
bool _allSatisfyInSelection(
|
||||
EditorState editorState, String styleKey, dynamic matchValue) {
|
||||
EditorState editorState,
|
||||
String styleKey,
|
||||
dynamic matchValue,
|
||||
) {
|
||||
final selection = editorState.service.selectionService.currentSelection.value;
|
||||
final nodes = editorState.service.selectionService.currentSelectedNodes;
|
||||
final textNodes = nodes.whereType<TextNode>().toList(growable: false);
|
||||
|
@ -38,7 +38,7 @@ class AppFlowyEditor extends StatefulWidget {
|
||||
this.customBuilders = const {},
|
||||
this.shortcutEvents = const [],
|
||||
this.selectionMenuItems = const [],
|
||||
this.editorStyle = const EditorStyle.defaultStyle(),
|
||||
required this.editorStyle,
|
||||
}) : super(key: key);
|
||||
|
||||
final EditorState editorState;
|
||||
@ -58,6 +58,8 @@ class AppFlowyEditor extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _AppFlowyEditorState extends State<AppFlowyEditor> {
|
||||
Widget? services;
|
||||
|
||||
EditorState get editorState => widget.editorState;
|
||||
|
||||
@override
|
||||
@ -75,19 +77,34 @@ class _AppFlowyEditorState extends State<AppFlowyEditor> {
|
||||
|
||||
if (editorState.service != oldWidget.editorState.service) {
|
||||
editorState.selectionMenuItems = widget.selectionMenuItems;
|
||||
editorState.editorStyle = widget.editorStyle;
|
||||
editorState.service.renderPluginService = _createRenderPlugin();
|
||||
}
|
||||
|
||||
editorState.editorStyle = widget.editorStyle;
|
||||
services = null;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
services ??= _buildServices(context);
|
||||
return Overlay(
|
||||
initialEntries: [
|
||||
OverlayEntry(
|
||||
builder: (context) => services!,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
AppFlowyScroll _buildServices(BuildContext context) {
|
||||
return AppFlowyScroll(
|
||||
key: editorState.service.scrollServiceKey,
|
||||
child: Padding(
|
||||
padding: widget.editorStyle.padding,
|
||||
child: AppFlowySelection(
|
||||
key: editorState.service.selectionServiceKey,
|
||||
cursorColor: widget.editorStyle.cursorColor,
|
||||
selectionColor: widget.editorStyle.selectionColor,
|
||||
editorState: editorState,
|
||||
child: AppFlowyInput(
|
||||
key: editorState.service.inputServiceKey,
|
||||
|
@ -1,8 +1,7 @@
|
||||
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||
import 'package:appflowy_editor/src/service/internal_key_event_handlers/number_list_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
|
||||
// Handle delete text.
|
||||
@ -42,12 +41,12 @@ KeyEventResult _handleBackspace(EditorState editorState, RawKeyEvent event) {
|
||||
if (index < 0 && selection.isCollapsed) {
|
||||
// 1. style
|
||||
if (textNode.subtype != null) {
|
||||
if (textNode.subtype == StyleKey.numberList) {
|
||||
if (textNode.subtype == BuiltInAttributeKey.numberList) {
|
||||
cancelNumberListPath = textNode.path;
|
||||
}
|
||||
transactionBuilder
|
||||
..updateNode(textNode, {
|
||||
StyleKey.subtype: null,
|
||||
BuiltInAttributeKey.subtype: null,
|
||||
textNode.subtype!: null,
|
||||
})
|
||||
..afterSelection = Selection.collapsed(
|
||||
@ -91,7 +90,8 @@ KeyEventResult _handleBackspace(EditorState editorState, RawKeyEvent event) {
|
||||
_deleteTextNodes(transactionBuilder, textNodes, selection);
|
||||
transactionBuilder.commit();
|
||||
|
||||
if (nodeAtStart is TextNode && nodeAtStart.subtype == StyleKey.numberList) {
|
||||
if (nodeAtStart is TextNode &&
|
||||
nodeAtStart.subtype == BuiltInAttributeKey.numberList) {
|
||||
makeFollowingNodesIncremental(
|
||||
editorState,
|
||||
startPosition.path,
|
||||
@ -130,7 +130,7 @@ KeyEventResult _backDeleteToPreviousTextNode(
|
||||
bool prevIsNumberList = false;
|
||||
while (previous != null) {
|
||||
if (previous is TextNode) {
|
||||
if (previous.subtype == StyleKey.numberList) {
|
||||
if (previous.subtype == BuiltInAttributeKey.numberList) {
|
||||
prevIsNumberList = true;
|
||||
}
|
||||
|
||||
@ -212,7 +212,8 @@ KeyEventResult _handleDelete(EditorState editorState, RawKeyEvent event) {
|
||||
_deleteTextNodes(transactionBuilder, textNodes, selection);
|
||||
transactionBuilder.commit();
|
||||
|
||||
if (nodeAtStart is TextNode && nodeAtStart.subtype == StyleKey.numberList) {
|
||||
if (nodeAtStart is TextNode &&
|
||||
nodeAtStart.subtype == BuiltInAttributeKey.numberList) {
|
||||
makeFollowingNodesIncremental(
|
||||
editorState, startPosition.path, transactionBuilder.afterSelection!);
|
||||
}
|
||||
@ -236,7 +237,7 @@ KeyEventResult _mergeNextLineIntoThisLine(
|
||||
transactionBuilder.deleteNode(nextNode);
|
||||
transactionBuilder.commit();
|
||||
|
||||
if (textNode.subtype == StyleKey.numberList) {
|
||||
if (textNode.subtype == BuiltInAttributeKey.numberList) {
|
||||
makeFollowingNodesIncremental(editorState, textNode.path, selection);
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/infra/html_converter.dart';
|
||||
import 'package:appflowy_editor/src/document/node_iterator.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
||||
import 'package:appflowy_editor/src/service/internal_key_event_handlers/number_list_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rich_clipboard/rich_clipboard.dart';
|
||||
@ -108,8 +108,8 @@ void _pasteMultipleLinesInText(
|
||||
|
||||
if (nodeAtPath.type == "text" && firstNode.type == "text") {
|
||||
int? startNumber;
|
||||
if (nodeAtPath.subtype == StyleKey.numberList) {
|
||||
startNumber = nodeAtPath.attributes[StyleKey.number] as int;
|
||||
if (nodeAtPath.subtype == BuiltInAttributeKey.numberList) {
|
||||
startNumber = nodeAtPath.attributes[BuiltInAttributeKey.number] as int;
|
||||
}
|
||||
|
||||
// split and merge
|
||||
|
@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy_editor/src/extensions/path_extensions.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
||||
import './number_list_helper.dart';
|
||||
|
||||
/// Handle some cases where enter is pressed and shift is not pressed.
|
||||
@ -59,7 +59,8 @@ ShortcutEventHandler enterWithoutShiftInTextNodesHandler =
|
||||
..afterSelection = afterSelection
|
||||
..commit();
|
||||
|
||||
if (startNode is TextNode && startNode.subtype == StyleKey.numberList) {
|
||||
if (startNode is TextNode &&
|
||||
startNode.subtype == BuiltInAttributeKey.numberList) {
|
||||
makeFollowingNodesIncremental(
|
||||
editorState, selection.start.path, afterSelection);
|
||||
}
|
||||
@ -82,17 +83,15 @@ ShortcutEventHandler enterWithoutShiftInTextNodesHandler =
|
||||
Position(path: textNode.path, offset: 0),
|
||||
);
|
||||
TransactionBuilder(editorState)
|
||||
..updateNode(
|
||||
textNode,
|
||||
Attributes.fromIterable(
|
||||
StyleKey.globalStyleKeys,
|
||||
value: (_) => null,
|
||||
))
|
||||
..updateNode(textNode, {
|
||||
BuiltInAttributeKey.subtype: null,
|
||||
})
|
||||
..afterSelection = afterSelection
|
||||
..commit();
|
||||
|
||||
final nextNode = textNode.next;
|
||||
if (nextNode is TextNode && nextNode.subtype == StyleKey.numberList) {
|
||||
if (nextNode is TextNode &&
|
||||
nextNode.subtype == BuiltInAttributeKey.numberList) {
|
||||
makeFollowingNodesIncremental(
|
||||
editorState, textNode.path, afterSelection,
|
||||
beginNum: 0);
|
||||
@ -103,11 +102,13 @@ ShortcutEventHandler enterWithoutShiftInTextNodesHandler =
|
||||
Position(path: textNode.path.next, offset: 0),
|
||||
);
|
||||
|
||||
if (subtype == StyleKey.numberList) {
|
||||
final prevNumber = textNode.attributes[StyleKey.number] as int;
|
||||
if (subtype == BuiltInAttributeKey.numberList) {
|
||||
final prevNumber =
|
||||
textNode.attributes[BuiltInAttributeKey.number] as int;
|
||||
final newNode = TextNode.empty();
|
||||
newNode.attributes[StyleKey.subtype] = StyleKey.numberList;
|
||||
newNode.attributes[StyleKey.number] = prevNumber;
|
||||
newNode.attributes[BuiltInAttributeKey.subtype] =
|
||||
BuiltInAttributeKey.numberList;
|
||||
newNode.attributes[BuiltInAttributeKey.number] = prevNumber;
|
||||
final insertPath = textNode.path;
|
||||
TransactionBuilder(editorState)
|
||||
..insertNode(
|
||||
@ -159,7 +160,7 @@ ShortcutEventHandler enterWithoutShiftInTextNodesHandler =
|
||||
|
||||
// If the new type of a text node is number list,
|
||||
// the numbers of the following nodes should be incremental.
|
||||
if (textNode.subtype == StyleKey.numberList) {
|
||||
if (textNode.subtype == BuiltInAttributeKey.numberList) {
|
||||
makeFollowingNodesIncremental(editorState, nextPath, afterSelection);
|
||||
}
|
||||
|
||||
@ -169,17 +170,17 @@ ShortcutEventHandler enterWithoutShiftInTextNodesHandler =
|
||||
Attributes _attributesFromPreviousLine(TextNode textNode) {
|
||||
final prevAttributes = textNode.attributes;
|
||||
final subType = textNode.subtype;
|
||||
if (subType == null || subType == StyleKey.heading) {
|
||||
if (subType == null || subType == BuiltInAttributeKey.heading) {
|
||||
return {};
|
||||
}
|
||||
|
||||
final copy = Attributes.from(prevAttributes);
|
||||
if (subType == StyleKey.numberList) {
|
||||
if (subType == BuiltInAttributeKey.numberList) {
|
||||
return _nextNumberAttributesFromPreviousLine(copy, textNode);
|
||||
}
|
||||
|
||||
if (subType == StyleKey.checkbox) {
|
||||
copy[StyleKey.checkbox] = false;
|
||||
if (subType == BuiltInAttributeKey.checkbox) {
|
||||
copy[BuiltInAttributeKey.checkbox] = false;
|
||||
return copy;
|
||||
}
|
||||
|
||||
@ -188,7 +189,7 @@ Attributes _attributesFromPreviousLine(TextNode textNode) {
|
||||
|
||||
Attributes _nextNumberAttributesFromPreviousLine(
|
||||
Attributes copy, TextNode textNode) {
|
||||
final prevNum = textNode.attributes[StyleKey.number] as int?;
|
||||
copy[StyleKey.number] = prevNum == null ? 1 : prevNum + 1;
|
||||
final prevNum = textNode.attributes[BuiltInAttributeKey.number] as int?;
|
||||
copy[BuiltInAttributeKey.number] = prevNum == null ? 1 : prevNum + 1;
|
||||
return copy;
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import 'package:appflowy_editor/src/service/default_text_operations/format_rich_text_style.dart';
|
||||
import 'package:appflowy_editor/src/service/shortcut_event/shortcut_event_handler.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy_editor/src/document/node.dart';
|
||||
import 'package:appflowy_editor/src/service/default_text_operations/format_rich_text_style.dart';
|
||||
|
||||
ShortcutEventHandler formatBoldEventHandler = (editorState, event) {
|
||||
final selection = editorState.service.selectionService.currentSelection.value;
|
||||
@ -55,7 +55,10 @@ ShortcutEventHandler formatHighlightEventHandler = (editorState, event) {
|
||||
if (selection == null || textNodes.isEmpty) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
formatHighlight(editorState);
|
||||
formatHighlight(
|
||||
editorState,
|
||||
editorState.editorStyle.textStyle.highlightColorHex,
|
||||
);
|
||||
return KeyEventResult.handled;
|
||||
};
|
||||
|
||||
@ -73,3 +76,14 @@ ShortcutEventHandler formatLinkEventHandler = (editorState, event) {
|
||||
}
|
||||
return KeyEventResult.ignored;
|
||||
};
|
||||
|
||||
ShortcutEventHandler formatEmbedCodeEventHandler = (editorState, event) {
|
||||
final selection = editorState.service.selectionService.currentSelection.value;
|
||||
final nodes = editorState.service.selectionService.currentSelectedNodes;
|
||||
final textNodes = nodes.whereType<TextNode>().toList(growable: false);
|
||||
if (selection == null || textNodes.isEmpty) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
formatEmbedCode(editorState);
|
||||
return KeyEventResult.ignored;
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:appflowy_editor/src/document/selection.dart';
|
||||
import 'package:appflowy_editor/src/editor_state.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
||||
import 'package:appflowy_editor/src/operation/transaction_builder.dart';
|
||||
import 'package:appflowy_editor/src/document/attributes.dart';
|
||||
|
||||
@ -11,7 +11,7 @@ void makeFollowingNodesIncremental(
|
||||
if (insertNode == null) {
|
||||
return;
|
||||
}
|
||||
beginNum ??= insertNode.attributes[StyleKey.number] as int;
|
||||
beginNum ??= insertNode.attributes[BuiltInAttributeKey.number] as int;
|
||||
|
||||
int numPtr = beginNum + 1;
|
||||
var ptr = insertNode.next;
|
||||
@ -19,13 +19,13 @@ void makeFollowingNodesIncremental(
|
||||
final builder = TransactionBuilder(editorState);
|
||||
|
||||
while (ptr != null) {
|
||||
if (ptr.subtype != StyleKey.numberList) {
|
||||
if (ptr.subtype != BuiltInAttributeKey.numberList) {
|
||||
break;
|
||||
}
|
||||
final currentNum = ptr.attributes[StyleKey.number] as int;
|
||||
final currentNum = ptr.attributes[BuiltInAttributeKey.number] as int;
|
||||
if (currentNum != numPtr) {
|
||||
Attributes updateAttributes = {};
|
||||
updateAttributes[StyleKey.number] = numPtr;
|
||||
updateAttributes[BuiltInAttributeKey.number] = numPtr;
|
||||
builder.updateNode(ptr, updateAttributes);
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,14 @@
|
||||
import 'package:appflowy_editor/src/service/shortcut_event/shortcut_event_handler.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
||||
import 'package:appflowy_editor/src/document/node.dart';
|
||||
import 'package:appflowy_editor/src/document/position.dart';
|
||||
import 'package:appflowy_editor/src/document/selection.dart';
|
||||
import 'package:appflowy_editor/src/editor_state.dart';
|
||||
import 'package:appflowy_editor/src/operation/transaction_builder.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||
import './number_list_helper.dart';
|
||||
import 'package:appflowy_editor/src/extensions/attributes_extension.dart';
|
||||
|
||||
@visibleForTesting
|
||||
List<String> get checkboxListSymbols => _checkboxListSymbols;
|
||||
@ -68,7 +68,7 @@ ShortcutEventHandler whiteSpaceHandler = (editorState, event) {
|
||||
|
||||
KeyEventResult _toNumberList(EditorState editorState, TextNode textNode,
|
||||
String matchText, String numText) {
|
||||
if (textNode.subtype == StyleKey.bulletedList) {
|
||||
if (textNode.subtype == BuiltInAttributeKey.bulletedList) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
@ -86,8 +86,9 @@ KeyEventResult _toNumberList(EditorState editorState, TextNode textNode,
|
||||
final prevNode = textNode.previous;
|
||||
if (prevNode != null &&
|
||||
prevNode is TextNode &&
|
||||
prevNode.attributes[StyleKey.subtype] == StyleKey.numberList) {
|
||||
final prevNumber = prevNode.attributes[StyleKey.number] as int;
|
||||
prevNode.attributes[BuiltInAttributeKey.subtype] ==
|
||||
BuiltInAttributeKey.numberList) {
|
||||
final prevNumber = prevNode.attributes[BuiltInAttributeKey.number] as int;
|
||||
if (numValue != prevNumber + 1) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
@ -102,8 +103,10 @@ KeyEventResult _toNumberList(EditorState editorState, TextNode textNode,
|
||||
|
||||
TransactionBuilder(editorState)
|
||||
..deleteText(textNode, 0, matchText.length)
|
||||
..updateNode(textNode,
|
||||
{StyleKey.subtype: StyleKey.numberList, StyleKey.number: numValue})
|
||||
..updateNode(textNode, {
|
||||
BuiltInAttributeKey.subtype: BuiltInAttributeKey.numberList,
|
||||
BuiltInAttributeKey.number: numValue
|
||||
})
|
||||
..afterSelection = afterSelection
|
||||
..commit();
|
||||
|
||||
@ -113,13 +116,13 @@ KeyEventResult _toNumberList(EditorState editorState, TextNode textNode,
|
||||
}
|
||||
|
||||
KeyEventResult _toBulletedList(EditorState editorState, TextNode textNode) {
|
||||
if (textNode.subtype == StyleKey.bulletedList) {
|
||||
if (textNode.subtype == BuiltInAttributeKey.bulletedList) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
TransactionBuilder(editorState)
|
||||
..deleteText(textNode, 0, 1)
|
||||
..updateNode(textNode, {
|
||||
StyleKey.subtype: StyleKey.bulletedList,
|
||||
BuiltInAttributeKey.subtype: BuiltInAttributeKey.bulletedList,
|
||||
})
|
||||
..afterSelection = Selection.collapsed(
|
||||
Position(
|
||||
@ -132,7 +135,7 @@ KeyEventResult _toBulletedList(EditorState editorState, TextNode textNode) {
|
||||
}
|
||||
|
||||
KeyEventResult _toCheckboxList(EditorState editorState, TextNode textNode) {
|
||||
if (textNode.subtype == StyleKey.checkbox) {
|
||||
if (textNode.subtype == BuiltInAttributeKey.checkbox) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
final String symbol;
|
||||
@ -152,8 +155,8 @@ KeyEventResult _toCheckboxList(EditorState editorState, TextNode textNode) {
|
||||
TransactionBuilder(editorState)
|
||||
..deleteText(textNode, 0, symbol.length)
|
||||
..updateNode(textNode, {
|
||||
StyleKey.subtype: StyleKey.checkbox,
|
||||
StyleKey.checkbox: check,
|
||||
BuiltInAttributeKey.subtype: BuiltInAttributeKey.checkbox,
|
||||
BuiltInAttributeKey.checkbox: check,
|
||||
})
|
||||
..afterSelection = Selection.collapsed(
|
||||
Position(
|
||||
@ -178,8 +181,8 @@ KeyEventResult _toHeadingStyle(
|
||||
TransactionBuilder(editorState)
|
||||
..deleteText(textNode, 0, x)
|
||||
..updateNode(textNode, {
|
||||
StyleKey.subtype: StyleKey.heading,
|
||||
StyleKey.heading: hX,
|
||||
BuiltInAttributeKey.subtype: BuiltInAttributeKey.heading,
|
||||
BuiltInAttributeKey.heading: hX,
|
||||
})
|
||||
..afterSelection = Selection.collapsed(
|
||||
Position(
|
||||
|
@ -82,7 +82,7 @@ abstract class AppFlowySelectionService {
|
||||
class AppFlowySelection extends StatefulWidget {
|
||||
const AppFlowySelection({
|
||||
Key? key,
|
||||
this.cursorColor = Colors.black,
|
||||
this.cursorColor = const Color(0xFF00BCF0),
|
||||
this.selectionColor = const Color.fromARGB(53, 111, 201, 231),
|
||||
required this.editorState,
|
||||
required this.child,
|
||||
@ -343,8 +343,10 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
|
||||
currentSelectedNodes = nodes;
|
||||
|
||||
// TODO: need to be refactored.
|
||||
Rect? topmostRect;
|
||||
Offset? toolbarOffset;
|
||||
LayerLink? layerLink;
|
||||
final editorOffset =
|
||||
editorState.renderBox?.localToGlobal(Offset.zero) ?? Offset.zero;
|
||||
|
||||
final backwardNodes =
|
||||
selection.isBackward ? nodes : nodes.reversed.toList(growable: false);
|
||||
@ -381,13 +383,20 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
|
||||
}
|
||||
}
|
||||
|
||||
const baseToolbarOffset = Offset(0, 35.0);
|
||||
final rects = selectable.getRectsInSelection(newSelection);
|
||||
for (final rect in rects) {
|
||||
// TODO: Need to compute more precise location.
|
||||
topmostRect ??= rect;
|
||||
layerLink ??= node.layerLink;
|
||||
final selectionRect = _transformRectToGlobal(selectable, rect);
|
||||
selectionRects.add(selectionRect);
|
||||
|
||||
selectionRects.add(_transformRectToGlobal(selectable, rect));
|
||||
// TODO: Need to compute more precise location.
|
||||
if ((selectionRect.topLeft.dy - editorOffset.dy) <=
|
||||
baseToolbarOffset.dy) {
|
||||
toolbarOffset ??= rect.bottomLeft;
|
||||
} else {
|
||||
toolbarOffset ??= rect.topLeft - baseToolbarOffset;
|
||||
}
|
||||
layerLink ??= node.layerLink;
|
||||
|
||||
final overlay = OverlayEntry(
|
||||
builder: (context) => SelectionWidget(
|
||||
@ -402,9 +411,11 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
|
||||
|
||||
Overlay.of(context)?.insertAll(_selectionAreas);
|
||||
|
||||
if (topmostRect != null && layerLink != null) {
|
||||
editorState.service.toolbarService
|
||||
?.showInOffset(topmostRect.topLeft, layerLink);
|
||||
if (toolbarOffset != null && layerLink != null) {
|
||||
editorState.service.toolbarService?.showInOffset(
|
||||
toolbarOffset,
|
||||
layerLink,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -144,6 +144,12 @@ List<ShortcutEvent> builtInShortcutEvents = [
|
||||
windowsCommand: 'ctrl+shift+h',
|
||||
handler: formatHighlightEventHandler,
|
||||
),
|
||||
ShortcutEvent(
|
||||
key: 'Format embed code',
|
||||
command: 'meta+e',
|
||||
windowsCommand: 'ctrl+e',
|
||||
handler: formatEmbedCodeEventHandler,
|
||||
),
|
||||
ShortcutEvent(
|
||||
key: 'Format link',
|
||||
command: 'meta+k',
|
||||
|
@ -44,7 +44,7 @@ class _FlowyToolbarState extends State<FlowyToolbar>
|
||||
key: _toolbarWidgetKey,
|
||||
editorState: widget.editorState,
|
||||
layerLink: layerLink,
|
||||
offset: offset.translate(0, -37.0),
|
||||
offset: offset,
|
||||
items: _filterItems(defaultToolbarItems),
|
||||
),
|
||||
);
|
||||
|
@ -22,6 +22,7 @@ dependencies:
|
||||
provider: ^6.0.3
|
||||
url_launcher: ^6.1.5
|
||||
logging: ^1.0.2
|
||||
intl_utils: ^2.7.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@ -66,3 +67,13 @@ flutter:
|
||||
#
|
||||
# For details regarding fonts in packages, see
|
||||
# https://flutter.dev/custom-fonts/#from-packages
|
||||
|
||||
flutter_intl:
|
||||
enabled: true
|
||||
class_name: AppFlowyEditorLocalizations
|
||||
main_locale: en
|
||||
arb_dir: lib/l10n
|
||||
output_dir: lib/src/l10n
|
||||
use_deferred_loading: false
|
||||
localizely:
|
||||
project_id: b7199c7d-eca0-4025-894d-230cdcafa9aa
|
||||
|
@ -23,18 +23,20 @@ class EditorWidgetTester {
|
||||
_editorState.service.selectionService.currentSelection.value;
|
||||
|
||||
Future<EditorWidgetTester> startTesting() async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: AppFlowyEditor(
|
||||
editorState: _editorState,
|
||||
editorStyle: const EditorStyle(
|
||||
padding: EdgeInsets.symmetric(vertical: 30),
|
||||
),
|
||||
),
|
||||
final app = MaterialApp(
|
||||
localizationsDelegates: const [
|
||||
AppFlowyEditorLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: AppFlowyEditorLocalizations.delegate.supportedLocales,
|
||||
home: Scaffold(
|
||||
body: AppFlowyEditor(
|
||||
editorState: _editorState,
|
||||
editorStyle: EditorStyle.defaultStyle(),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpWidget(app);
|
||||
await tester.pump();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -118,6 +118,9 @@ extension on LogicalKeyboardKey {
|
||||
if (this == LogicalKeyboardKey.keyC) {
|
||||
return PhysicalKeyboardKey.keyC;
|
||||
}
|
||||
if (this == LogicalKeyboardKey.keyE) {
|
||||
return PhysicalKeyboardKey.keyE;
|
||||
}
|
||||
if (this == LogicalKeyboardKey.keyI) {
|
||||
return PhysicalKeyboardKey.keyI;
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/default_selectable.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||
import 'package:appflowy_editor/src/extensions/text_node_extensions.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import '../../infra/test_editor.dart';
|
||||
import 'package:appflowy_editor/src/extensions/attributes_extension.dart';
|
||||
|
||||
void main() async {
|
||||
setUpAll(() {
|
||||
@ -25,15 +26,15 @@ void main() async {
|
||||
..insertTextNode(
|
||||
'',
|
||||
attributes: {
|
||||
StyleKey.subtype: StyleKey.checkbox,
|
||||
StyleKey.checkbox: false,
|
||||
BuiltInAttributeKey.subtype: BuiltInAttributeKey.checkbox,
|
||||
BuiltInAttributeKey.checkbox: false,
|
||||
},
|
||||
delta: Delta([
|
||||
TextInsert(text, {
|
||||
StyleKey.bold: true,
|
||||
StyleKey.italic: true,
|
||||
StyleKey.underline: true,
|
||||
StyleKey.strikethrough: true,
|
||||
BuiltInAttributeKey.bold: true,
|
||||
BuiltInAttributeKey.italic: true,
|
||||
BuiltInAttributeKey.underline: true,
|
||||
BuiltInAttributeKey.strikethrough: true,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
@ -1,10 +1,11 @@
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
||||
import 'package:appflowy_editor/src/extensions/text_node_extensions.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||
import 'package:appflowy_editor/src/render/toolbar/toolbar_item_widget.dart';
|
||||
import 'package:appflowy_editor/src/render/toolbar/toolbar_widget.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import '../../infra/test_editor.dart';
|
||||
import 'package:appflowy_editor/src/extensions/attributes_extension.dart';
|
||||
|
||||
void main() async {
|
||||
setUpAll(() {
|
||||
@ -229,7 +230,7 @@ void main() async {
|
||||
expect(
|
||||
node.allSatisfyInSelection(
|
||||
code,
|
||||
StyleKey.code,
|
||||
BuiltInAttributeKey.code,
|
||||
(value) {
|
||||
return value == true;
|
||||
},
|
||||
@ -319,7 +320,7 @@ void main() async {
|
||||
expect(
|
||||
node.allSatisfyInSelection(
|
||||
selection,
|
||||
StyleKey.backgroundColor,
|
||||
BuiltInAttributeKey.backgroundColor,
|
||||
(value) {
|
||||
return value == blue;
|
||||
},
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
||||
import 'package:appflowy_editor/src/render/selection_menu/selection_menu_item_widget.dart';
|
||||
import 'package:appflowy_editor/src/render/selection_menu/selection_menu_service.dart';
|
||||
import 'package:appflowy_editor/src/render/selection_menu/selection_menu_widget.dart';
|
||||
@ -13,91 +13,99 @@ void main() async {
|
||||
});
|
||||
|
||||
group('selection_menu_widget.dart', () {
|
||||
for (var i = 0; i < defaultSelectionMenuItems.length; i++) {
|
||||
testWidgets('Selects number.$i item in selection menu', (tester) async {
|
||||
final editor = await _prepare(tester);
|
||||
for (var j = 0; j < i; j++) {
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.arrowDown);
|
||||
}
|
||||
// const i = defaultSelectionMenuItems.length;
|
||||
//
|
||||
// Because the `defaultSelectionMenuItems` uses localization,
|
||||
// and the MaterialApp has not been initialized at the time of getting the value,
|
||||
// it will crash.
|
||||
//
|
||||
// Use const value temporarily instead.
|
||||
const i = 7;
|
||||
testWidgets('Selects number.$i item in selection menu', (tester) async {
|
||||
final editor = await _prepare(tester);
|
||||
for (var j = 0; j < i; j++) {
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.arrowDown);
|
||||
}
|
||||
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.enter);
|
||||
expect(
|
||||
find.byType(SelectionMenuWidget, skipOffstage: false),
|
||||
findsNothing,
|
||||
);
|
||||
if (defaultSelectionMenuItems[i].name != 'Image') {
|
||||
await _testDefaultSelectionMenuItems(i, editor);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.enter);
|
||||
expect(
|
||||
find.byType(SelectionMenuWidget, skipOffstage: false),
|
||||
findsNothing,
|
||||
);
|
||||
if (defaultSelectionMenuItems[i].name != 'Image') {
|
||||
await _testDefaultSelectionMenuItems(i, editor);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('Search item in selection menu util no results', (tester) async {
|
||||
final editor = await _prepare(tester);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.keyT);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.keyE);
|
||||
expect(
|
||||
find.byType(SelectionMenuItemWidget, skipOffstage: false),
|
||||
findsNWidgets(3),
|
||||
);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.backspace);
|
||||
expect(
|
||||
find.byType(SelectionMenuItemWidget, skipOffstage: false),
|
||||
findsNWidgets(4),
|
||||
);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.keyE);
|
||||
expect(
|
||||
find.byType(SelectionMenuItemWidget, skipOffstage: false),
|
||||
findsNWidgets(3),
|
||||
);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.keyX);
|
||||
expect(
|
||||
find.byType(SelectionMenuItemWidget, skipOffstage: false),
|
||||
findsNWidgets(1),
|
||||
);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.keyT);
|
||||
expect(
|
||||
find.byType(SelectionMenuItemWidget, skipOffstage: false),
|
||||
findsNWidgets(1),
|
||||
);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.keyT);
|
||||
expect(
|
||||
find.byType(SelectionMenuItemWidget, skipOffstage: false),
|
||||
findsNothing,
|
||||
);
|
||||
});
|
||||
testWidgets('Search item in selection menu util no results',
|
||||
(tester) async {
|
||||
final editor = await _prepare(tester);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.keyT);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.keyE);
|
||||
expect(
|
||||
find.byType(SelectionMenuItemWidget, skipOffstage: false),
|
||||
findsNWidgets(3),
|
||||
);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.backspace);
|
||||
expect(
|
||||
find.byType(SelectionMenuItemWidget, skipOffstage: false),
|
||||
findsNWidgets(4),
|
||||
);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.keyE);
|
||||
expect(
|
||||
find.byType(SelectionMenuItemWidget, skipOffstage: false),
|
||||
findsNWidgets(3),
|
||||
);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.keyX);
|
||||
expect(
|
||||
find.byType(SelectionMenuItemWidget, skipOffstage: false),
|
||||
findsNWidgets(1),
|
||||
);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.keyT);
|
||||
expect(
|
||||
find.byType(SelectionMenuItemWidget, skipOffstage: false),
|
||||
findsNWidgets(1),
|
||||
);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.keyT);
|
||||
expect(
|
||||
find.byType(SelectionMenuItemWidget, skipOffstage: false),
|
||||
findsNothing,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Search item in selection menu and presses esc', (tester) async {
|
||||
final editor = await _prepare(tester);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.keyT);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.keyE);
|
||||
expect(
|
||||
find.byType(SelectionMenuItemWidget, skipOffstage: false),
|
||||
findsNWidgets(3),
|
||||
);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.escape);
|
||||
expect(
|
||||
find.byType(SelectionMenuItemWidget, skipOffstage: false),
|
||||
findsNothing,
|
||||
);
|
||||
});
|
||||
testWidgets('Search item in selection menu and presses esc',
|
||||
(tester) async {
|
||||
final editor = await _prepare(tester);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.keyT);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.keyE);
|
||||
expect(
|
||||
find.byType(SelectionMenuItemWidget, skipOffstage: false),
|
||||
findsNWidgets(3),
|
||||
);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.escape);
|
||||
expect(
|
||||
find.byType(SelectionMenuItemWidget, skipOffstage: false),
|
||||
findsNothing,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Search item in selection menu and presses backspace',
|
||||
(tester) async {
|
||||
final editor = await _prepare(tester);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.keyT);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.keyE);
|
||||
expect(
|
||||
find.byType(SelectionMenuItemWidget, skipOffstage: false),
|
||||
findsNWidgets(3),
|
||||
);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.backspace);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.backspace);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.backspace);
|
||||
expect(
|
||||
find.byType(SelectionMenuItemWidget, skipOffstage: false),
|
||||
findsNothing,
|
||||
);
|
||||
testWidgets('Search item in selection menu and presses backspace',
|
||||
(tester) async {
|
||||
final editor = await _prepare(tester);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.keyT);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.keyE);
|
||||
expect(
|
||||
find.byType(SelectionMenuItemWidget, skipOffstage: false),
|
||||
findsNWidgets(3),
|
||||
);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.backspace);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.backspace);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.backspace);
|
||||
expect(
|
||||
find.byType(SelectionMenuItemWidget, skipOffstage: false),
|
||||
findsNothing,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -135,18 +143,18 @@ Future<void> _testDefaultSelectionMenuItems(
|
||||
if (item.name == 'Text') {
|
||||
expect(node?.subtype == null, true);
|
||||
} else if (item.name == 'Heading 1') {
|
||||
expect(node?.subtype, StyleKey.heading);
|
||||
expect(node?.attributes.heading, StyleKey.h1);
|
||||
expect(node?.subtype, BuiltInAttributeKey.heading);
|
||||
expect(node?.attributes.heading, BuiltInAttributeKey.h1);
|
||||
} else if (item.name == 'Heading 2') {
|
||||
expect(node?.subtype, StyleKey.heading);
|
||||
expect(node?.attributes.heading, StyleKey.h2);
|
||||
expect(node?.subtype, BuiltInAttributeKey.heading);
|
||||
expect(node?.attributes.heading, BuiltInAttributeKey.h2);
|
||||
} else if (item.name == 'Heading 3') {
|
||||
expect(node?.subtype, StyleKey.heading);
|
||||
expect(node?.attributes.heading, StyleKey.h3);
|
||||
expect(node?.subtype, BuiltInAttributeKey.heading);
|
||||
expect(node?.attributes.heading, BuiltInAttributeKey.h3);
|
||||
} else if (item.name == 'Bulleted list') {
|
||||
expect(node?.subtype, StyleKey.bulletedList);
|
||||
expect(node?.subtype, BuiltInAttributeKey.bulletedList);
|
||||
} else if (item.name == 'Checkbox') {
|
||||
expect(node?.subtype, StyleKey.checkbox);
|
||||
expect(node?.subtype, BuiltInAttributeKey.checkbox);
|
||||
expect(node?.attributes.check, false);
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/render/image/image_node_widget.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:network_image_mock/network_image_mock.dart';
|
||||
import '../../infra/test_editor.dart';
|
||||
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
||||
import 'package:appflowy_editor/src/extensions/attributes_extension.dart';
|
||||
|
||||
void main() async {
|
||||
setUpAll(() {
|
||||
@ -132,17 +133,18 @@ void main() async {
|
||||
//
|
||||
testWidgets('Presses backspace key in styled text (checkbox)',
|
||||
(tester) async {
|
||||
await _deleteStyledTextByBackspace(tester, StyleKey.checkbox);
|
||||
await _deleteStyledTextByBackspace(tester, BuiltInAttributeKey.checkbox);
|
||||
});
|
||||
testWidgets('Presses backspace key in styled text (bulletedList)',
|
||||
(tester) async {
|
||||
await _deleteStyledTextByBackspace(tester, StyleKey.bulletedList);
|
||||
await _deleteStyledTextByBackspace(
|
||||
tester, BuiltInAttributeKey.bulletedList);
|
||||
});
|
||||
testWidgets('Presses backspace key in styled text (heading)', (tester) async {
|
||||
await _deleteStyledTextByBackspace(tester, StyleKey.heading);
|
||||
await _deleteStyledTextByBackspace(tester, BuiltInAttributeKey.heading);
|
||||
});
|
||||
testWidgets('Presses backspace key in styled text (quote)', (tester) async {
|
||||
await _deleteStyledTextByBackspace(tester, StyleKey.quote);
|
||||
await _deleteStyledTextByBackspace(tester, BuiltInAttributeKey.quote);
|
||||
});
|
||||
|
||||
// Before
|
||||
@ -157,17 +159,17 @@ void main() async {
|
||||
// [Style] Welcome to Appflowy 😁
|
||||
//
|
||||
testWidgets('Presses delete key in styled text (checkbox)', (tester) async {
|
||||
await _deleteStyledTextByDelete(tester, StyleKey.checkbox);
|
||||
await _deleteStyledTextByDelete(tester, BuiltInAttributeKey.checkbox);
|
||||
});
|
||||
testWidgets('Presses delete key in styled text (bulletedList)',
|
||||
(tester) async {
|
||||
await _deleteStyledTextByDelete(tester, StyleKey.bulletedList);
|
||||
await _deleteStyledTextByDelete(tester, BuiltInAttributeKey.bulletedList);
|
||||
});
|
||||
testWidgets('Presses delete key in styled text (heading)', (tester) async {
|
||||
await _deleteStyledTextByDelete(tester, StyleKey.heading);
|
||||
await _deleteStyledTextByDelete(tester, BuiltInAttributeKey.heading);
|
||||
});
|
||||
testWidgets('Presses delete key in styled text (quote)', (tester) async {
|
||||
await _deleteStyledTextByDelete(tester, StyleKey.quote);
|
||||
await _deleteStyledTextByDelete(tester, BuiltInAttributeKey.quote);
|
||||
});
|
||||
|
||||
// Before
|
||||
@ -250,7 +252,7 @@ void main() async {
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.space);
|
||||
expect(
|
||||
(editor.nodeAtPath([0]) as TextNode).attributes.heading,
|
||||
StyleKey.h1,
|
||||
BuiltInAttributeKey.h1,
|
||||
);
|
||||
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.backspace);
|
||||
@ -263,7 +265,7 @@ void main() async {
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.space);
|
||||
expect(
|
||||
(editor.nodeAtPath([0]) as TextNode).attributes.heading,
|
||||
StyleKey.h1,
|
||||
BuiltInAttributeKey.h1,
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -330,14 +332,14 @@ Future<void> _deleteStyledTextByBackspace(
|
||||
WidgetTester tester, String style) async {
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
Attributes attributes = {
|
||||
StyleKey.subtype: style,
|
||||
BuiltInAttributeKey.subtype: style,
|
||||
};
|
||||
if (style == StyleKey.checkbox) {
|
||||
attributes[StyleKey.checkbox] = true;
|
||||
} else if (style == StyleKey.numberList) {
|
||||
attributes[StyleKey.number] = 1;
|
||||
} else if (style == StyleKey.heading) {
|
||||
attributes[StyleKey.heading] = StyleKey.h1;
|
||||
if (style == BuiltInAttributeKey.checkbox) {
|
||||
attributes[BuiltInAttributeKey.checkbox] = true;
|
||||
} else if (style == BuiltInAttributeKey.numberList) {
|
||||
attributes[BuiltInAttributeKey.number] = 1;
|
||||
} else if (style == BuiltInAttributeKey.heading) {
|
||||
attributes[BuiltInAttributeKey.heading] = BuiltInAttributeKey.h1;
|
||||
}
|
||||
final editor = tester.editor
|
||||
..insertTextNode(text)
|
||||
@ -377,14 +379,14 @@ Future<void> _deleteStyledTextByDelete(
|
||||
WidgetTester tester, String style) async {
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
Attributes attributes = {
|
||||
StyleKey.subtype: style,
|
||||
BuiltInAttributeKey.subtype: style,
|
||||
};
|
||||
if (style == StyleKey.checkbox) {
|
||||
attributes[StyleKey.checkbox] = true;
|
||||
} else if (style == StyleKey.numberList) {
|
||||
attributes[StyleKey.number] = 1;
|
||||
} else if (style == StyleKey.heading) {
|
||||
attributes[StyleKey.heading] = StyleKey.h1;
|
||||
if (style == BuiltInAttributeKey.checkbox) {
|
||||
attributes[BuiltInAttributeKey.checkbox] = true;
|
||||
} else if (style == BuiltInAttributeKey.numberList) {
|
||||
attributes[BuiltInAttributeKey.number] = 1;
|
||||
} else if (style == BuiltInAttributeKey.heading) {
|
||||
attributes[BuiltInAttributeKey.heading] = BuiltInAttributeKey.h1;
|
||||
}
|
||||
final editor = tester.editor
|
||||
..insertTextNode(text)
|
||||
|
@ -1,8 +1,8 @@
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import '../../infra/test_editor.dart';
|
||||
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
||||
|
||||
void main() async {
|
||||
setUpAll(() {
|
||||
@ -95,16 +95,16 @@ void main() async {
|
||||
// [Style] Welcome to Appflowy 😁
|
||||
// [Style]
|
||||
testWidgets('Presses enter key in bulleted list', (tester) async {
|
||||
await _testStyleNeedToBeCopy(tester, StyleKey.bulletedList);
|
||||
await _testStyleNeedToBeCopy(tester, BuiltInAttributeKey.bulletedList);
|
||||
});
|
||||
testWidgets('Presses enter key in numbered list', (tester) async {
|
||||
await _testStyleNeedToBeCopy(tester, StyleKey.numberList);
|
||||
await _testStyleNeedToBeCopy(tester, BuiltInAttributeKey.numberList);
|
||||
});
|
||||
testWidgets('Presses enter key in checkbox styled text', (tester) async {
|
||||
await _testStyleNeedToBeCopy(tester, StyleKey.checkbox);
|
||||
await _testStyleNeedToBeCopy(tester, BuiltInAttributeKey.checkbox);
|
||||
});
|
||||
testWidgets('Presses enter key in quoted text', (tester) async {
|
||||
await _testStyleNeedToBeCopy(tester, StyleKey.quote);
|
||||
await _testStyleNeedToBeCopy(tester, BuiltInAttributeKey.quote);
|
||||
});
|
||||
|
||||
testWidgets('Presses enter key in multiple selection from top to bottom',
|
||||
@ -143,12 +143,12 @@ void main() async {
|
||||
Future<void> _testStyleNeedToBeCopy(WidgetTester tester, String style) async {
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
Attributes attributes = {
|
||||
StyleKey.subtype: style,
|
||||
BuiltInAttributeKey.subtype: style,
|
||||
};
|
||||
if (style == StyleKey.checkbox) {
|
||||
attributes[StyleKey.checkbox] = true;
|
||||
} else if (style == StyleKey.numberList) {
|
||||
attributes[StyleKey.number] = 1;
|
||||
if (style == BuiltInAttributeKey.checkbox) {
|
||||
attributes[BuiltInAttributeKey.checkbox] = true;
|
||||
} else if (style == BuiltInAttributeKey.numberList) {
|
||||
attributes[BuiltInAttributeKey.number] = 1;
|
||||
}
|
||||
final editor = tester.editor
|
||||
..insertTextNode(text)
|
||||
|
@ -2,24 +2,24 @@ import 'dart:io';
|
||||
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/render/link_menu/link_menu.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||
import 'package:appflowy_editor/src/extensions/text_node_extensions.dart';
|
||||
import 'package:appflowy_editor/src/render/toolbar/toolbar_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import '../../infra/test_editor.dart';
|
||||
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
||||
|
||||
void main() async {
|
||||
setUpAll(() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
});
|
||||
|
||||
group('update_text_style_by_command_x_handler.dart', () {
|
||||
group('format_style_handler.dart', () {
|
||||
testWidgets('Presses Command + B to update text style', (tester) async {
|
||||
await _testUpdateTextStyleByCommandX(
|
||||
tester,
|
||||
StyleKey.bold,
|
||||
BuiltInAttributeKey.bold,
|
||||
true,
|
||||
LogicalKeyboardKey.keyB,
|
||||
);
|
||||
@ -27,7 +27,7 @@ void main() async {
|
||||
testWidgets('Presses Command + I to update text style', (tester) async {
|
||||
await _testUpdateTextStyleByCommandX(
|
||||
tester,
|
||||
StyleKey.italic,
|
||||
BuiltInAttributeKey.italic,
|
||||
true,
|
||||
LogicalKeyboardKey.keyI,
|
||||
);
|
||||
@ -35,7 +35,7 @@ void main() async {
|
||||
testWidgets('Presses Command + U to update text style', (tester) async {
|
||||
await _testUpdateTextStyleByCommandX(
|
||||
tester,
|
||||
StyleKey.underline,
|
||||
BuiltInAttributeKey.underline,
|
||||
true,
|
||||
LogicalKeyboardKey.keyU,
|
||||
);
|
||||
@ -44,7 +44,7 @@ void main() async {
|
||||
(tester) async {
|
||||
await _testUpdateTextStyleByCommandX(
|
||||
tester,
|
||||
StyleKey.strikethrough,
|
||||
BuiltInAttributeKey.strikethrough,
|
||||
true,
|
||||
LogicalKeyboardKey.keyS,
|
||||
);
|
||||
@ -52,10 +52,11 @@ void main() async {
|
||||
|
||||
testWidgets('Presses Command + Shift + H to update text style',
|
||||
(tester) async {
|
||||
// FIXME: customize the highlight color instead of using magic number.
|
||||
await _testUpdateTextStyleByCommandX(
|
||||
tester,
|
||||
StyleKey.backgroundColor,
|
||||
defaultHighlightColor,
|
||||
BuiltInAttributeKey.backgroundColor,
|
||||
'0x6000BCF0',
|
||||
LogicalKeyboardKey.keyH,
|
||||
);
|
||||
});
|
||||
@ -63,6 +64,15 @@ void main() async {
|
||||
testWidgets('Presses Command + K to trigger link menu', (tester) async {
|
||||
await _testLinkMenuInSingleTextSelection(tester);
|
||||
});
|
||||
|
||||
testWidgets('Presses Command + E to update text style', (tester) async {
|
||||
await _testUpdateTextStyleByCommandX(
|
||||
tester,
|
||||
BuiltInAttributeKey.code,
|
||||
true,
|
||||
LogicalKeyboardKey.keyE,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -256,7 +266,7 @@ Future<void> _testLinkMenuInSingleTextSelection(WidgetTester tester) async {
|
||||
expect(
|
||||
node.allSatisfyInSelection(
|
||||
selection,
|
||||
StyleKey.href,
|
||||
BuiltInAttributeKey.href,
|
||||
(value) => value == link,
|
||||
),
|
||||
true);
|
||||
@ -293,7 +303,7 @@ Future<void> _testLinkMenuInSingleTextSelection(WidgetTester tester) async {
|
||||
expect(
|
||||
node.allSatisfyInSelection(
|
||||
selection,
|
||||
StyleKey.href,
|
||||
BuiltInAttributeKey.href,
|
||||
(value) => value == link,
|
||||
),
|
||||
false);
|
@ -1,9 +1,10 @@
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||
import 'package:appflowy_editor/src/service/internal_key_event_handlers/whitespace_handler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import '../../infra/test_editor.dart';
|
||||
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
||||
import 'package:appflowy_editor/src/extensions/attributes_extension.dart';
|
||||
|
||||
void main() async {
|
||||
setUpAll(() {
|
||||
@ -45,8 +46,8 @@ void main() async {
|
||||
|
||||
final textNode = (editor.nodeAtPath([i - 1]) as TextNode);
|
||||
|
||||
expect(textNode.subtype, StyleKey.heading);
|
||||
// StyleKey.h1 ~ StyleKey.h6
|
||||
expect(textNode.subtype, BuiltInAttributeKey.heading);
|
||||
// BuiltInAttributeKey.h1 ~ BuiltInAttributeKey.h6
|
||||
expect(textNode.attributes.heading, 'h$i');
|
||||
}
|
||||
});
|
||||
@ -85,8 +86,8 @@ void main() async {
|
||||
|
||||
final textNode = (editor.nodeAtPath([i - 1]) as TextNode);
|
||||
|
||||
expect(textNode.subtype, StyleKey.heading);
|
||||
// StyleKey.h1 ~ StyleKey.h6
|
||||
expect(textNode.subtype, BuiltInAttributeKey.heading);
|
||||
// BuiltInAttributeKey.h1 ~ BuiltInAttributeKey.h6
|
||||
expect(textNode.attributes.heading, 'h$i');
|
||||
expect(textNode.toRawString().startsWith('##'), true);
|
||||
}
|
||||
@ -117,8 +118,8 @@ void main() async {
|
||||
await editor.insertText(textNode, '#' * i, 0);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.space);
|
||||
|
||||
expect(textNode.subtype, StyleKey.heading);
|
||||
// StyleKey.h2 ~ StyleKey.h6
|
||||
expect(textNode.subtype, BuiltInAttributeKey.heading);
|
||||
// BuiltInAttributeKey.h2 ~ BuiltInAttributeKey.h6
|
||||
expect(textNode.attributes.heading, 'h$i');
|
||||
}
|
||||
});
|
||||
@ -136,7 +137,7 @@ void main() async {
|
||||
);
|
||||
await editor.insertText(textNode, symbol, 0);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.space);
|
||||
expect(textNode.subtype, StyleKey.checkbox);
|
||||
expect(textNode.subtype, BuiltInAttributeKey.checkbox);
|
||||
expect(textNode.attributes.check, false);
|
||||
}
|
||||
});
|
||||
@ -154,7 +155,7 @@ void main() async {
|
||||
);
|
||||
await editor.insertText(textNode, symbol, 0);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.space);
|
||||
expect(textNode.subtype, StyleKey.checkbox);
|
||||
expect(textNode.subtype, BuiltInAttributeKey.checkbox);
|
||||
expect(textNode.attributes.check, true);
|
||||
}
|
||||
});
|
||||
@ -171,7 +172,7 @@ void main() async {
|
||||
);
|
||||
await editor.insertText(textNode, symbol, 0);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.space);
|
||||
expect(textNode.subtype, StyleKey.bulletedList);
|
||||
expect(textNode.subtype, BuiltInAttributeKey.bulletedList);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -81,6 +81,8 @@ void main() async {
|
||||
editor.documentSelection,
|
||||
Selection.single(path: [1], startOffset: 0),
|
||||
);
|
||||
|
||||
tester.pumpAndSettle();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||
import 'package:appflowy_editor/src/render/toolbar/toolbar_item.dart';
|
||||
import 'package:appflowy_editor/src/render/toolbar/toolbar_item_widget.dart';
|
||||
import 'package:appflowy_editor/src/render/toolbar/toolbar_widget.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import '../infra/test_editor.dart';
|
||||
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
||||
|
||||
void main() async {
|
||||
setUpAll(() {
|
||||
@ -45,13 +45,13 @@ void main() async {
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Test toolbar service in single text selection with StyleKey.partialStyleKeys',
|
||||
'Test toolbar service in single text selection with BuiltInAttributeKey.partialStyleKeys',
|
||||
(tester) async {
|
||||
final attributes = StyleKey.partialStyleKeys.fold<Attributes>({},
|
||||
(previousValue, element) {
|
||||
if (element == StyleKey.backgroundColor) {
|
||||
final attributes = BuiltInAttributeKey.partialStyleKeys
|
||||
.fold<Attributes>({}, (previousValue, element) {
|
||||
if (element == BuiltInAttributeKey.backgroundColor) {
|
||||
previousValue[element] = '0x6000BCF0';
|
||||
} else if (element == StyleKey.href) {
|
||||
} else if (element == BuiltInAttributeKey.href) {
|
||||
previousValue[element] = 'appflowy.io';
|
||||
} else {
|
||||
previousValue[element] = true;
|
||||
@ -77,11 +77,11 @@ void main() async {
|
||||
expect(find.byType(ToolbarWidget), findsOneWidget);
|
||||
|
||||
void testHighlight(bool expectedValue) {
|
||||
for (final styleKey in StyleKey.partialStyleKeys) {
|
||||
for (final styleKey in BuiltInAttributeKey.partialStyleKeys) {
|
||||
var key = styleKey;
|
||||
if (styleKey == StyleKey.backgroundColor) {
|
||||
if (styleKey == BuiltInAttributeKey.backgroundColor) {
|
||||
key = 'highlight';
|
||||
} else if (styleKey == StyleKey.href) {
|
||||
} else if (styleKey == BuiltInAttributeKey.href) {
|
||||
key = 'link';
|
||||
} else {
|
||||
continue;
|
||||
@ -116,22 +116,24 @@ void main() async {
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Test toolbar service in single text selection with StyleKey.globalStyleKeys',
|
||||
'Test toolbar service in single text selection with BuiltInAttributeKey.globalStyleKeys',
|
||||
(tester) async {
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
|
||||
final editor = tester.editor
|
||||
..insertTextNode(text, attributes: {
|
||||
StyleKey.subtype: StyleKey.heading,
|
||||
StyleKey.heading: StyleKey.h1,
|
||||
BuiltInAttributeKey.subtype: BuiltInAttributeKey.heading,
|
||||
BuiltInAttributeKey.heading: BuiltInAttributeKey.h1,
|
||||
})
|
||||
..insertTextNode(
|
||||
text,
|
||||
attributes: {StyleKey.subtype: StyleKey.quote},
|
||||
attributes: {BuiltInAttributeKey.subtype: BuiltInAttributeKey.quote},
|
||||
)
|
||||
..insertTextNode(
|
||||
text,
|
||||
attributes: {StyleKey.subtype: StyleKey.bulletedList},
|
||||
attributes: {
|
||||
BuiltInAttributeKey.subtype: BuiltInAttributeKey.bulletedList
|
||||
},
|
||||
);
|
||||
await editor.startTesting();
|
||||
|
||||
@ -167,12 +169,12 @@ void main() async {
|
||||
..insertTextNode(
|
||||
null,
|
||||
attributes: {
|
||||
StyleKey.subtype: StyleKey.heading,
|
||||
StyleKey.heading: StyleKey.h1,
|
||||
BuiltInAttributeKey.subtype: BuiltInAttributeKey.heading,
|
||||
BuiltInAttributeKey.heading: BuiltInAttributeKey.h1,
|
||||
},
|
||||
delta: Delta([
|
||||
TextInsert(text, {
|
||||
StyleKey.bold: true,
|
||||
BuiltInAttributeKey.bold: true,
|
||||
})
|
||||
]),
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user