From 8c13d1b4c1e33cdfa3dcff4cda67f8adb60433aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chiotti?= <44336112+maelchiotti@users.noreply.github.com> Date: Sun, 9 Jun 2024 17:40:52 +0200 Subject: [PATCH 1/9] feat: update theming of inline code, code blocks, quotes and horizontal rules for dark themes --- packages/fleather/lib/src/widgets/editor.dart | 9 ++++++--- packages/fleather/lib/src/widgets/theme.dart | 15 +++++++++------ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/fleather/lib/src/widgets/editor.dart b/packages/fleather/lib/src/widgets/editor.dart index 3b2d36be..795f2919 100644 --- a/packages/fleather/lib/src/widgets/editor.dart +++ b/packages/fleather/lib/src/widgets/editor.dart @@ -81,11 +81,14 @@ typedef FleatherEmbedBuilder = Widget Function( /// Only supports "horizontal rule" embeds. Widget defaultFleatherEmbedBuilder(BuildContext context, EmbedNode node) { if (node.value.type == 'hr') { - final theme = FleatherTheme.of(context)!; + final theme = Theme.of(context); + final fleatherTheme = FleatherTheme.of(context)!; + return Divider( - height: theme.paragraph.style.fontSize! * theme.paragraph.style.height!, + height: fleatherTheme.paragraph.style.fontSize! * + fleatherTheme.paragraph.style.height!, thickness: 2, - color: Colors.grey.shade200, + color: theme.colorScheme.surfaceContainerHigh, ); } throw UnimplementedError( diff --git a/packages/fleather/lib/src/widgets/theme.dart b/packages/fleather/lib/src/widgets/theme.dart index 76fd5211..17afd531 100644 --- a/packages/fleather/lib/src/widgets/theme.dart +++ b/packages/fleather/lib/src/widgets/theme.dart @@ -148,7 +148,7 @@ class FleatherThemeData { final inlineCodeStyle = TextStyle( fontSize: 14, - color: themeData.colorScheme.primary.withOpacity(0.8), + color: themeData.colorScheme.primary, fontFamily: fontFamily, ); @@ -158,8 +158,8 @@ class FleatherThemeData { underline: const TextStyle(decoration: TextDecoration.underline), strikethrough: const TextStyle(decoration: TextDecoration.lineThrough), inlineCode: InlineCodeThemeData( - backgroundColor: Colors.grey.shade100, - radius: const Radius.circular(3), + backgroundColor: themeData.colorScheme.surfaceContainerHigh, + radius: const Radius.circular(2), style: inlineCodeStyle, heading1: inlineCodeStyle.copyWith( fontSize: 32, @@ -245,20 +245,23 @@ class FleatherThemeData { lineSpacing: const VerticalSpacing(top: 6, bottom: 2), decoration: BoxDecoration( border: BorderDirectional( - start: BorderSide(width: 4, color: Colors.grey.shade300), + start: BorderSide( + width: 4, + color: themeData.colorScheme.surfaceContainerHigh, + ), ), ), ), code: TextBlockTheme( style: TextStyle( - color: Colors.blue.shade900.withOpacity(0.9), + color: themeData.colorScheme.primary, fontFamily: fontFamily, fontSize: 13.0, height: 1.4, ), spacing: baseSpacing, decoration: BoxDecoration( - color: Colors.grey.shade50, + color: themeData.colorScheme.surfaceContainerHigh, borderRadius: BorderRadius.circular(2), ), ), From ec952b6b562d1e9f973a78dffabad0c6645f2054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chiotti?= <44336112+maelchiotti@users.noreply.github.com> Date: Sat, 3 Aug 2024 00:02:45 +0200 Subject: [PATCH 2/9] feat: add FleatherLocalizations InheritedWidget with link dialog and headings localizations --- packages/fleather/lib/fleather.dart | 5 +- .../lib/src/widgets/editor_toolbar.dart | 38 +++-- .../src/widgets/fleather_localizations.dart | 148 ++++++++++++++++++ packages/fleather/lib/src/widgets/theme.dart | 8 +- 4 files changed, 178 insertions(+), 21 deletions(-) create mode 100644 packages/fleather/lib/src/widgets/fleather_localizations.dart diff --git a/packages/fleather/lib/fleather.dart b/packages/fleather/lib/fleather.dart index 5236ad6a..c716c0ea 100644 --- a/packages/fleather/lib/fleather.dart +++ b/packages/fleather/lib/fleather.dart @@ -6,13 +6,14 @@ library fleather; export 'package:parchment/parchment.dart'; export 'src/rendering/editor.dart'; +export 'src/services/clipboard_manager.dart'; +export 'src/widgets/autoformats.dart'; export 'src/widgets/controller.dart'; export 'src/widgets/cursor.dart'; export 'src/widgets/editor.dart'; export 'src/widgets/editor_toolbar.dart'; export 'src/widgets/field.dart'; +export 'src/widgets/fleather_localizations.dart'; export 'src/widgets/link.dart' show LinkActionPickerDelegate, LinkMenuAction; export 'src/widgets/text_line.dart'; export 'src/widgets/theme.dart'; -export 'src/widgets/autoformats.dart'; -export 'src/services/clipboard_manager.dart'; diff --git a/packages/fleather/lib/src/widgets/editor_toolbar.dart b/packages/fleather/lib/src/widgets/editor_toolbar.dart index 27eb3a1d..ae39756c 100644 --- a/packages/fleather/lib/src/widgets/editor_toolbar.dart +++ b/packages/fleather/lib/src/widgets/editor_toolbar.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:fleather/src/widgets/editor.dart'; +import 'package:fleather/src/widgets/fleather_localizations.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:parchment/parchment.dart'; @@ -176,7 +177,9 @@ class _LinkStyleButtonState extends State { showDialog( context: context, builder: (ctx) { - return const _LinkDialog(); + return _LinkDialog( + localizations: FleatherLocalizations.of(context), + ); }, ).then(_linkSubmitted); } @@ -190,7 +193,9 @@ class _LinkStyleButtonState extends State { } class _LinkDialog extends StatefulWidget { - const _LinkDialog(); + const _LinkDialog({required this.localizations}); + + final FleatherLocalizationsData localizations; @override _LinkDialogState createState() => _LinkDialogState(); @@ -203,14 +208,16 @@ class _LinkDialogState extends State<_LinkDialog> { Widget build(BuildContext context) { return AlertDialog( content: TextField( - decoration: const InputDecoration(labelText: 'Paste a link'), + decoration: InputDecoration( + labelText: widget.localizations.linkDialogPasteALink, + ), autofocus: true, onChanged: _linkChanged, ), actions: [ TextButton( onPressed: _link.isNotEmpty ? _applyLink : null, - child: const Text('Apply'), + child: Text(widget.localizations.linkDialogApply), ), ], ); @@ -568,16 +575,6 @@ class SelectHeadingButton extends StatefulWidget { State createState() => _SelectHeadingButtonState(); } -final _headingToText = { - ParchmentAttribute.heading.unset: 'Normal', - ParchmentAttribute.heading.level1: 'Heading 1', - ParchmentAttribute.heading.level2: 'Heading 2', - ParchmentAttribute.heading.level3: 'Heading 3', - ParchmentAttribute.heading.level4: 'Heading 4', - ParchmentAttribute.heading.level5: 'Heading 5', - ParchmentAttribute.heading.level6: 'Heading 6', -}; - class _SelectHeadingButtonState extends State { static double buttonHeight = 32; @@ -619,6 +616,10 @@ class _SelectHeadingButtonState extends State { @override Widget build(BuildContext context) { + final currentHeading = FleatherLocalizations.of(context) + .headingsLocalizations + .headingsToText[current]; + return ConstrainedBox( constraints: BoxConstraints.tightFor(height: buttonHeight), child: RawMaterialButton( @@ -636,7 +637,7 @@ class _SelectHeadingButtonState extends State { toolbar.requestKeyboard(); } }, - child: Text(_headingToText[current] ?? ''), + child: Text(currentHeading ?? ''), ), ); } @@ -666,13 +667,18 @@ class _HeadingList extends StatelessWidget { @override Widget build(BuildContext context) { + final headings = FleatherLocalizations.of(context) + .headingsLocalizations + .headingsToText + .entries; + return SingleChildScrollView( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 200), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, - children: _headingToText.entries + children: headings .map((entry) => _listItem(theme, entry.key, entry.value)) .toList(), ), diff --git a/packages/fleather/lib/src/widgets/fleather_localizations.dart b/packages/fleather/lib/src/widgets/fleather_localizations.dart new file mode 100644 index 00000000..67df5786 --- /dev/null +++ b/packages/fleather/lib/src/widgets/fleather_localizations.dart @@ -0,0 +1,148 @@ +import 'package:fleather/fleather.dart'; +import 'package:flutter/material.dart'; + +/// Provides localizations to descendant widgets. +/// +/// Descendant widgets obtain the current theme's [FleatherLocalizations] +/// object using [FleatherLocalizations.of]. +/// +/// See also: +/// - [FleatherLocalizationsData], which contains actual localizations. + +class FleatherLocalizations extends InheritedWidget { + /// Localizations. + final FleatherLocalizationsData data; + + const FleatherLocalizations({ + super.key, + required this.data, + required super.child, + }); + + @override + bool updateShouldNotify(FleatherLocalizations oldWidget) { + return data != oldWidget.data; + } + + /// The data from the closest [FleatherLocalizationsData] instance + /// that encloses the given [context]. + static FleatherLocalizationsData of(BuildContext context) { + final FleatherLocalizations? widget = + context.dependOnInheritedWidgetOfExactType(); + + assert( + widget != null, + '${FleatherLocalizations.of} called with a context that does not contain a $FleatherLocalizations.', + ); + + return widget!.data; + } +} + +/// Localizations data. +class FleatherLocalizationsData { + /// Headings labels in the toolbar. + final HeadingsLocalizations headingsLocalizations; + + /// Label of the text field to enter a link in the link dialog. + final String linkDialogPasteALink; + + /// Button label to apply the link entered in the link dialog. + final String linkDialogApply; + + FleatherLocalizationsData({ + required this.headingsLocalizations, + required this.linkDialogPasteALink, + required this.linkDialogApply, + }); + + /// Default localizations. + factory FleatherLocalizationsData.fallback() { + return FleatherLocalizationsData( + headingsLocalizations: HeadingsLocalizations.fallback(), + linkDialogPasteALink: 'Paste a link', + linkDialogApply: 'Apply', + ); + } + + FleatherLocalizationsData copyWith({ + HeadingsLocalizations? headingsLocalizations, + String? linkDialogPasteALink, + String? linkDialogApply, + }) { + return FleatherLocalizationsData( + headingsLocalizations: + headingsLocalizations ?? this.headingsLocalizations, + linkDialogPasteALink: linkDialogPasteALink ?? this.linkDialogPasteALink, + linkDialogApply: linkDialogApply ?? this.linkDialogApply, + ); + } + + FleatherLocalizationsData merge(FleatherLocalizationsData other) { + return copyWith( + headingsLocalizations: other.headingsLocalizations, + linkDialogPasteALink: other.linkDialogPasteALink, + linkDialogApply: other.linkDialogApply, + ); + } +} + +/// Localizations for the headings formatters in the toolbar. +class HeadingsLocalizations { + /// Normal heading. + final String headingNormal; + + /// Level 1 heading. + final String headingLevel1; + + /// Level 2 heading. + final String headingLevel2; + + /// Level 3 heading. + final String headingLevel3; + + /// Level 4 heading. + final String headingLevel4; + + /// Level 5 heading. + final String headingLevel5; + + /// Level 6 heading. + final String headingLevel6; + + HeadingsLocalizations({ + required this.headingNormal, + required this.headingLevel1, + required this.headingLevel2, + required this.headingLevel3, + required this.headingLevel4, + required this.headingLevel5, + required this.headingLevel6, + }); + + /// Default localizations. + factory HeadingsLocalizations.fallback() { + return HeadingsLocalizations( + headingNormal: 'Normal', + headingLevel1: 'Heading 1', + headingLevel2: 'Heading 2', + headingLevel3: 'Heading 3', + headingLevel4: 'Heading 4', + headingLevel5: 'Heading 5', + headingLevel6: 'Heading 6', + ); + } + + /// Returns every heading localization mapped to its [ParchmentAttribute]. + Map, String> get headingsToText { + return { + ParchmentAttribute.heading.unset: headingNormal, + ParchmentAttribute.heading.level1: headingLevel1, + ParchmentAttribute.heading.level2: headingLevel2, + ParchmentAttribute.heading.level3: headingLevel3, + ParchmentAttribute.heading.level4: headingLevel4, + ParchmentAttribute.heading.level5: headingLevel5, + ParchmentAttribute.heading.level6: headingLevel6, + }; + } +} diff --git a/packages/fleather/lib/src/widgets/theme.dart b/packages/fleather/lib/src/widgets/theme.dart index 156c538d..0cb3a73c 100644 --- a/packages/fleather/lib/src/widgets/theme.dart +++ b/packages/fleather/lib/src/widgets/theme.dart @@ -29,7 +29,7 @@ class FleatherTheme extends InheritedWidget { } /// The data from the closest [FleatherTheme] instance that encloses the given - /// context. + /// [context]. /// /// Returns `null` if there is no [FleatherTheme] in the given build context /// and [nullOk] is set to `true`. If [nullOk] is set to `false` (default) @@ -37,8 +37,10 @@ class FleatherTheme extends InheritedWidget { static FleatherThemeData? of(BuildContext context, {bool nullOk = false}) { final widget = context.dependOnInheritedWidgetOfExactType(); if (widget == null && nullOk) return null; - assert(widget != null, - '$FleatherTheme.of() called with a context that does not contain a FleatherTheme.'); + assert( + widget != null, + '${FleatherTheme.of} called with a context that does not contain a $FleatherTheme.', + ); return widget!.data; } } From d0013aacee3a0bf52f119dc4cc16fe4d9189e26e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chiotti?= <44336112+maelchiotti@users.noreply.github.com> Date: Mon, 2 Sep 2024 13:11:24 +0200 Subject: [PATCH 3/9] feat: add support for underline attribute with strict encoding parameter --- packages/fleather/lib/fleather.dart | 1 - .../lib/l10n/fleather_localizations.g.dart | 2 + .../lib/l10n/fleather_localizations_en.g.dart | 2 + .../lib/l10n/fleather_localizations_fa.g.dart | 2 + .../lib/l10n/fleather_localizations_fr.g.dart | 2 + .../src/widgets/fleather_localizations.dart | 148 ------------------ .../parchment/lib/src/codecs/markdown.dart | 66 +++++--- packages/parchment/lib/src/document/line.dart | 6 + 8 files changed, 60 insertions(+), 169 deletions(-) delete mode 100644 packages/fleather/lib/src/widgets/fleather_localizations.dart diff --git a/packages/fleather/lib/fleather.dart b/packages/fleather/lib/fleather.dart index 15ee787f..6477aa41 100644 --- a/packages/fleather/lib/fleather.dart +++ b/packages/fleather/lib/fleather.dart @@ -14,7 +14,6 @@ export 'src/widgets/cursor.dart'; export 'src/widgets/editor.dart'; export 'src/widgets/editor_toolbar.dart'; export 'src/widgets/field.dart'; -export 'src/widgets/fleather_localizations.dart'; export 'src/widgets/link.dart' show LinkActionPickerDelegate, LinkMenuAction; export 'src/widgets/text_line.dart'; export 'src/widgets/theme.dart'; diff --git a/packages/fleather/lib/l10n/fleather_localizations.g.dart b/packages/fleather/lib/l10n/fleather_localizations.g.dart index e6e023cb..fb4cf913 100644 --- a/packages/fleather/lib/l10n/fleather_localizations.g.dart +++ b/packages/fleather/lib/l10n/fleather_localizations.g.dart @@ -9,6 +9,8 @@ import 'fleather_localizations_en.g.dart'; import 'fleather_localizations_fa.g.dart'; import 'fleather_localizations_fr.g.dart'; +// ignore_for_file: type=lint + /// Callers can lookup localized strings with an instance of FleatherLocalizations /// returned by `FleatherLocalizations.of(context)`. /// diff --git a/packages/fleather/lib/l10n/fleather_localizations_en.g.dart b/packages/fleather/lib/l10n/fleather_localizations_en.g.dart index 28938095..f9a2eccb 100644 --- a/packages/fleather/lib/l10n/fleather_localizations_en.g.dart +++ b/packages/fleather/lib/l10n/fleather_localizations_en.g.dart @@ -1,5 +1,7 @@ import 'fleather_localizations.g.dart'; +// ignore_for_file: type=lint + /// The translations for English (`en`). class FleatherLocalizationsEn extends FleatherLocalizations { FleatherLocalizationsEn([String locale = 'en']) : super(locale); diff --git a/packages/fleather/lib/l10n/fleather_localizations_fa.g.dart b/packages/fleather/lib/l10n/fleather_localizations_fa.g.dart index c0c739d8..ac4e56b4 100644 --- a/packages/fleather/lib/l10n/fleather_localizations_fa.g.dart +++ b/packages/fleather/lib/l10n/fleather_localizations_fa.g.dart @@ -1,5 +1,7 @@ import 'fleather_localizations.g.dart'; +// ignore_for_file: type=lint + /// The translations for Persian (`fa`). class FleatherLocalizationsFa extends FleatherLocalizations { FleatherLocalizationsFa([String locale = 'fa']) : super(locale); diff --git a/packages/fleather/lib/l10n/fleather_localizations_fr.g.dart b/packages/fleather/lib/l10n/fleather_localizations_fr.g.dart index 6d21f012..e2e3fe0c 100644 --- a/packages/fleather/lib/l10n/fleather_localizations_fr.g.dart +++ b/packages/fleather/lib/l10n/fleather_localizations_fr.g.dart @@ -1,5 +1,7 @@ import 'fleather_localizations.g.dart'; +// ignore_for_file: type=lint + /// The translations for French (`fr`). class FleatherLocalizationsFr extends FleatherLocalizations { FleatherLocalizationsFr([String locale = 'fr']) : super(locale); diff --git a/packages/fleather/lib/src/widgets/fleather_localizations.dart b/packages/fleather/lib/src/widgets/fleather_localizations.dart deleted file mode 100644 index 67df5786..00000000 --- a/packages/fleather/lib/src/widgets/fleather_localizations.dart +++ /dev/null @@ -1,148 +0,0 @@ -import 'package:fleather/fleather.dart'; -import 'package:flutter/material.dart'; - -/// Provides localizations to descendant widgets. -/// -/// Descendant widgets obtain the current theme's [FleatherLocalizations] -/// object using [FleatherLocalizations.of]. -/// -/// See also: -/// - [FleatherLocalizationsData], which contains actual localizations. - -class FleatherLocalizations extends InheritedWidget { - /// Localizations. - final FleatherLocalizationsData data; - - const FleatherLocalizations({ - super.key, - required this.data, - required super.child, - }); - - @override - bool updateShouldNotify(FleatherLocalizations oldWidget) { - return data != oldWidget.data; - } - - /// The data from the closest [FleatherLocalizationsData] instance - /// that encloses the given [context]. - static FleatherLocalizationsData of(BuildContext context) { - final FleatherLocalizations? widget = - context.dependOnInheritedWidgetOfExactType(); - - assert( - widget != null, - '${FleatherLocalizations.of} called with a context that does not contain a $FleatherLocalizations.', - ); - - return widget!.data; - } -} - -/// Localizations data. -class FleatherLocalizationsData { - /// Headings labels in the toolbar. - final HeadingsLocalizations headingsLocalizations; - - /// Label of the text field to enter a link in the link dialog. - final String linkDialogPasteALink; - - /// Button label to apply the link entered in the link dialog. - final String linkDialogApply; - - FleatherLocalizationsData({ - required this.headingsLocalizations, - required this.linkDialogPasteALink, - required this.linkDialogApply, - }); - - /// Default localizations. - factory FleatherLocalizationsData.fallback() { - return FleatherLocalizationsData( - headingsLocalizations: HeadingsLocalizations.fallback(), - linkDialogPasteALink: 'Paste a link', - linkDialogApply: 'Apply', - ); - } - - FleatherLocalizationsData copyWith({ - HeadingsLocalizations? headingsLocalizations, - String? linkDialogPasteALink, - String? linkDialogApply, - }) { - return FleatherLocalizationsData( - headingsLocalizations: - headingsLocalizations ?? this.headingsLocalizations, - linkDialogPasteALink: linkDialogPasteALink ?? this.linkDialogPasteALink, - linkDialogApply: linkDialogApply ?? this.linkDialogApply, - ); - } - - FleatherLocalizationsData merge(FleatherLocalizationsData other) { - return copyWith( - headingsLocalizations: other.headingsLocalizations, - linkDialogPasteALink: other.linkDialogPasteALink, - linkDialogApply: other.linkDialogApply, - ); - } -} - -/// Localizations for the headings formatters in the toolbar. -class HeadingsLocalizations { - /// Normal heading. - final String headingNormal; - - /// Level 1 heading. - final String headingLevel1; - - /// Level 2 heading. - final String headingLevel2; - - /// Level 3 heading. - final String headingLevel3; - - /// Level 4 heading. - final String headingLevel4; - - /// Level 5 heading. - final String headingLevel5; - - /// Level 6 heading. - final String headingLevel6; - - HeadingsLocalizations({ - required this.headingNormal, - required this.headingLevel1, - required this.headingLevel2, - required this.headingLevel3, - required this.headingLevel4, - required this.headingLevel5, - required this.headingLevel6, - }); - - /// Default localizations. - factory HeadingsLocalizations.fallback() { - return HeadingsLocalizations( - headingNormal: 'Normal', - headingLevel1: 'Heading 1', - headingLevel2: 'Heading 2', - headingLevel3: 'Heading 3', - headingLevel4: 'Heading 4', - headingLevel5: 'Heading 5', - headingLevel6: 'Heading 6', - ); - } - - /// Returns every heading localization mapped to its [ParchmentAttribute]. - Map, String> get headingsToText { - return { - ParchmentAttribute.heading.unset: headingNormal, - ParchmentAttribute.heading.level1: headingLevel1, - ParchmentAttribute.heading.level2: headingLevel2, - ParchmentAttribute.heading.level3: headingLevel3, - ParchmentAttribute.heading.level4: headingLevel4, - ParchmentAttribute.heading.level5: headingLevel5, - ParchmentAttribute.heading.level6: headingLevel6, - }; - } -} diff --git a/packages/parchment/lib/src/codecs/markdown.dart b/packages/parchment/lib/src/codecs/markdown.dart index c6e314e9..42dd2c4c 100644 --- a/packages/parchment/lib/src/codecs/markdown.dart +++ b/packages/parchment/lib/src/codecs/markdown.dart @@ -1,15 +1,17 @@ import 'dart:convert'; -import 'package:parchment_delta/parchment_delta.dart'; - -import '../document.dart'; -import '../document/attributes.dart'; -import '../document/block.dart'; -import '../document/leaf.dart'; -import '../document/line.dart'; +import 'package:parchment/parchment.dart'; class ParchmentMarkdownCodec extends Codec { - const ParchmentMarkdownCodec(); + const ParchmentMarkdownCodec({this.strictEncoding = true}); + + /// Whether to strictly stick to the Markdown syntax during the encoding. + /// + /// If this option is enabled, during the encoding, if attributes that are + /// not natively supported by the Markdown syntax exist, an exception will be + /// thrown. Otherwise, they will be converted in the best way possible + /// (for example with HTML tags, plain text or placeholders). + final bool strictEncoding; @override Converter get decoder => @@ -17,7 +19,7 @@ class ParchmentMarkdownCodec extends Codec { @override Converter get encoder => - _ParchmentMarkdownEncoder(); + _ParchmentMarkdownEncoder(strict: strictEncoding); } class _ParchmentMarkdownDecoder extends Converter { @@ -354,6 +356,10 @@ class _ParchmentMarkdownDecoder extends Converter { } class _ParchmentMarkdownEncoder extends Converter { + const _ParchmentMarkdownEncoder({required this.strict}); + + final bool strict; + static final simpleBlocks = { ParchmentAttribute.bq: '> ', ParchmentAttribute.ul: '* ', @@ -403,8 +409,6 @@ class _ParchmentMarkdownEncoder extends Converter { ParchmentAttribute? currentBlockAttribute; void handleLine(LineNode node) { - if (node.hasBlockEmbed) return; - for (final attr in node.style.lineAttributes) { if (attr.key == ParchmentAttribute.block.key) { if (currentBlockAttribute != attr) { @@ -486,8 +490,11 @@ class _ParchmentMarkdownEncoder extends Converter { return buffer.toString(); } - void _writeAttribute(StringBuffer buffer, ParchmentAttribute? attribute, - {bool close = false}) { + void _writeAttribute( + StringBuffer buffer, + ParchmentAttribute? attribute, { + bool close = false, + }) { if (attribute == ParchmentAttribute.bold) { _writeBoldTag(buffer); } else if (attribute == ParchmentAttribute.italic) { @@ -497,18 +504,26 @@ class _ParchmentMarkdownEncoder extends Converter { } else if (attribute == ParchmentAttribute.strikethrough) { _writeStrikeThoughTag(buffer); } else if (attribute?.key == ParchmentAttribute.link.key) { - _writeLinkTag(buffer, attribute as ParchmentAttribute, - close: close); + _writeLinkTag( + buffer, + attribute as ParchmentAttribute, + close: close, + ); } else if (attribute?.key == ParchmentAttribute.heading.key) { _writeHeadingTag(buffer, attribute as ParchmentAttribute); } else if (attribute?.key == ParchmentAttribute.block.key) { - _writeBlockTag(buffer, attribute as ParchmentAttribute, - close: close); + _writeBlockTag( + buffer, + attribute as ParchmentAttribute, + close: close, + ); } else if (attribute?.key == ParchmentAttribute.checked.key) { // no-op already handled in handleBlock } else if (attribute?.key == ParchmentAttribute.indent.key) { // no-op already handled in handleBlock - } else { + } else if (!strict && attribute?.key == ParchmentAttribute.underline.key) { + _writeUnderlineTag(buffer, close: close); + } else if (strict) { throw ArgumentError('Cannot handle $attribute'); } } @@ -521,6 +536,14 @@ class _ParchmentMarkdownEncoder extends Converter { buffer.write('_'); } + void _writeUnderlineTag(StringBuffer buffer, {bool close = false}) { + if (false) { + buffer.write(''); + } else { + buffer.write(''); + } + } + void _writeInlineCodeTag(StringBuffer buffer) { buffer.write('`'); } @@ -529,8 +552,11 @@ class _ParchmentMarkdownEncoder extends Converter { buffer.write('~~'); } - void _writeLinkTag(StringBuffer buffer, ParchmentAttribute link, - {bool close = false}) { + void _writeLinkTag( + StringBuffer buffer, + ParchmentAttribute link, { + bool close = false, + }) { if (close) { buffer.write('](${link.value})'); } else { diff --git a/packages/parchment/lib/src/document/line.dart b/packages/parchment/lib/src/document/line.dart index 3d330a5c..f174a0fc 100644 --- a/packages/parchment/lib/src/document/line.dart +++ b/packages/parchment/lib/src/document/line.dart @@ -26,6 +26,12 @@ final class LineNode extends ContainerNode with StyledNode { return false; } + EmbedNode get embedNode { + assert(hasBlockEmbed); + + return children.single as EmbedNode; + } + /// Returns next [LineNode] or `null` if this is the last line in the document. LineNode? get nextLine { if (isLast) { From 0472ce10148bdc0fbb0364e71c13a95879f3daf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chiotti?= <44336112+maelchiotti@users.noreply.github.com> Date: Mon, 2 Sep 2024 13:11:49 +0200 Subject: [PATCH 4/9] feat: add support for horizontal line --- packages/parchment/lib/src/codecs/markdown.dart | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/parchment/lib/src/codecs/markdown.dart b/packages/parchment/lib/src/codecs/markdown.dart index 42dd2c4c..ed621765 100644 --- a/packages/parchment/lib/src/codecs/markdown.dart +++ b/packages/parchment/lib/src/codecs/markdown.dart @@ -409,6 +409,14 @@ class _ParchmentMarkdownEncoder extends Converter { ParchmentAttribute? currentBlockAttribute; void handleLine(LineNode node) { + if (node.hasBlockEmbed) { + if (node.embedNode.value == BlockEmbed.horizontalRule) { + _writeHorizontalLineTag(buffer); + } + + return; + } + for (final attr in node.style.lineAttributes) { if (attr.key == ParchmentAttribute.block.key) { if (currentBlockAttribute != attr) { @@ -586,4 +594,8 @@ class _ParchmentMarkdownEncoder extends Converter { } } } + + void _writeHorizontalLineTag(StringBuffer buffer) { + buffer.write('---'); + } } From 2ed078e61454c0417cc8f8a6e045c7520a169416 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chiotti?= <44336112+maelchiotti@users.noreply.github.com> Date: Wed, 4 Sep 2024 12:01:17 +0200 Subject: [PATCH 5/9] fix: dead code --- packages/parchment/lib/src/codecs/markdown.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/parchment/lib/src/codecs/markdown.dart b/packages/parchment/lib/src/codecs/markdown.dart index ed621765..dcad64cc 100644 --- a/packages/parchment/lib/src/codecs/markdown.dart +++ b/packages/parchment/lib/src/codecs/markdown.dart @@ -545,7 +545,7 @@ class _ParchmentMarkdownEncoder extends Converter { } void _writeUnderlineTag(StringBuffer buffer, {bool close = false}) { - if (false) { + if (close) { buffer.write(''); } else { buffer.write(''); From 5fb9f5a2e7bbcde981436c98f19a083f41146ba6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chiotti?= <44336112+maelchiotti@users.noreply.github.com> Date: Sun, 13 Oct 2024 17:38:42 +0200 Subject: [PATCH 6/9] fix: remove edits on localization files --- packages/fleather/lib/l10n/fleather_localizations.g.dart | 2 -- packages/fleather/lib/l10n/fleather_localizations_en.g.dart | 2 -- packages/fleather/lib/l10n/fleather_localizations_fa.g.dart | 2 -- packages/fleather/lib/l10n/fleather_localizations_fr.g.dart | 2 -- 4 files changed, 8 deletions(-) diff --git a/packages/fleather/lib/l10n/fleather_localizations.g.dart b/packages/fleather/lib/l10n/fleather_localizations.g.dart index fb4cf913..e6e023cb 100644 --- a/packages/fleather/lib/l10n/fleather_localizations.g.dart +++ b/packages/fleather/lib/l10n/fleather_localizations.g.dart @@ -9,8 +9,6 @@ import 'fleather_localizations_en.g.dart'; import 'fleather_localizations_fa.g.dart'; import 'fleather_localizations_fr.g.dart'; -// ignore_for_file: type=lint - /// Callers can lookup localized strings with an instance of FleatherLocalizations /// returned by `FleatherLocalizations.of(context)`. /// diff --git a/packages/fleather/lib/l10n/fleather_localizations_en.g.dart b/packages/fleather/lib/l10n/fleather_localizations_en.g.dart index f9a2eccb..28938095 100644 --- a/packages/fleather/lib/l10n/fleather_localizations_en.g.dart +++ b/packages/fleather/lib/l10n/fleather_localizations_en.g.dart @@ -1,7 +1,5 @@ import 'fleather_localizations.g.dart'; -// ignore_for_file: type=lint - /// The translations for English (`en`). class FleatherLocalizationsEn extends FleatherLocalizations { FleatherLocalizationsEn([String locale = 'en']) : super(locale); diff --git a/packages/fleather/lib/l10n/fleather_localizations_fa.g.dart b/packages/fleather/lib/l10n/fleather_localizations_fa.g.dart index ac4e56b4..c0c739d8 100644 --- a/packages/fleather/lib/l10n/fleather_localizations_fa.g.dart +++ b/packages/fleather/lib/l10n/fleather_localizations_fa.g.dart @@ -1,7 +1,5 @@ import 'fleather_localizations.g.dart'; -// ignore_for_file: type=lint - /// The translations for Persian (`fa`). class FleatherLocalizationsFa extends FleatherLocalizations { FleatherLocalizationsFa([String locale = 'fa']) : super(locale); diff --git a/packages/fleather/lib/l10n/fleather_localizations_fr.g.dart b/packages/fleather/lib/l10n/fleather_localizations_fr.g.dart index e2e3fe0c..6d21f012 100644 --- a/packages/fleather/lib/l10n/fleather_localizations_fr.g.dart +++ b/packages/fleather/lib/l10n/fleather_localizations_fr.g.dart @@ -1,7 +1,5 @@ import 'fleather_localizations.g.dart'; -// ignore_for_file: type=lint - /// The translations for French (`fr`). class FleatherLocalizationsFr extends FleatherLocalizations { FleatherLocalizationsFr([String locale = 'fr']) : super(locale); From 48065493111b5af438cf21d00269ae317d88e6f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chiotti?= <44336112+maelchiotti@users.noreply.github.com> Date: Sun, 13 Oct 2024 17:38:53 +0200 Subject: [PATCH 7/9] fix: review feedback --- .../parchment/lib/src/codecs/markdown.dart | 31 +++++++++---------- packages/parchment/lib/src/document/line.dart | 6 ---- 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/packages/parchment/lib/src/codecs/markdown.dart b/packages/parchment/lib/src/codecs/markdown.dart index ed621765..9645a436 100644 --- a/packages/parchment/lib/src/codecs/markdown.dart +++ b/packages/parchment/lib/src/codecs/markdown.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:parchment/parchment.dart'; +import '../../parchment.dart'; class ParchmentMarkdownCodec extends Codec { const ParchmentMarkdownCodec({this.strictEncoding = true}); @@ -11,6 +11,9 @@ class ParchmentMarkdownCodec extends Codec { /// not natively supported by the Markdown syntax exist, an exception will be /// thrown. Otherwise, they will be converted in the best way possible /// (for example with HTML tags, plain text or placeholders). + /// + /// Currently supported attributes: + /// - Underline with `...` final bool strictEncoding; @override @@ -410,8 +413,11 @@ class _ParchmentMarkdownEncoder extends Converter { void handleLine(LineNode node) { if (node.hasBlockEmbed) { - if (node.embedNode.value == BlockEmbed.horizontalRule) { + if ((node.children.single as EmbedNode).value == + BlockEmbed.horizontalRule) { _writeHorizontalLineTag(buffer); + } else { + buffer.write('[object]'); } return; @@ -498,11 +504,8 @@ class _ParchmentMarkdownEncoder extends Converter { return buffer.toString(); } - void _writeAttribute( - StringBuffer buffer, - ParchmentAttribute? attribute, { - bool close = false, - }) { + void _writeAttribute(StringBuffer buffer, ParchmentAttribute? attribute, + {bool close = false}) { if (attribute == ParchmentAttribute.bold) { _writeBoldTag(buffer); } else if (attribute == ParchmentAttribute.italic) { @@ -512,19 +515,13 @@ class _ParchmentMarkdownEncoder extends Converter { } else if (attribute == ParchmentAttribute.strikethrough) { _writeStrikeThoughTag(buffer); } else if (attribute?.key == ParchmentAttribute.link.key) { - _writeLinkTag( - buffer, - attribute as ParchmentAttribute, - close: close, - ); + _writeLinkTag(buffer, attribute as ParchmentAttribute, + close: close); } else if (attribute?.key == ParchmentAttribute.heading.key) { _writeHeadingTag(buffer, attribute as ParchmentAttribute); } else if (attribute?.key == ParchmentAttribute.block.key) { - _writeBlockTag( - buffer, - attribute as ParchmentAttribute, - close: close, - ); + _writeBlockTag(buffer, attribute as ParchmentAttribute, + close: close); } else if (attribute?.key == ParchmentAttribute.checked.key) { // no-op already handled in handleBlock } else if (attribute?.key == ParchmentAttribute.indent.key) { diff --git a/packages/parchment/lib/src/document/line.dart b/packages/parchment/lib/src/document/line.dart index f174a0fc..3d330a5c 100644 --- a/packages/parchment/lib/src/document/line.dart +++ b/packages/parchment/lib/src/document/line.dart @@ -26,12 +26,6 @@ final class LineNode extends ContainerNode with StyledNode { return false; } - EmbedNode get embedNode { - assert(hasBlockEmbed); - - return children.single as EmbedNode; - } - /// Returns next [LineNode] or `null` if this is the last line in the document. LineNode? get nextLine { if (isLast) { From b197443130e4e0c4a16bd2d2d65e11695d0ebbdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chiotti?= <44336112+maelchiotti@users.noreply.github.com> Date: Sun, 13 Oct 2024 17:49:12 +0200 Subject: [PATCH 8/9] feat: handle unsupported attributes with '[object]' --- packages/parchment/lib/src/codecs/markdown.dart | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/parchment/lib/src/codecs/markdown.dart b/packages/parchment/lib/src/codecs/markdown.dart index 9645a436..c0f22cd5 100644 --- a/packages/parchment/lib/src/codecs/markdown.dart +++ b/packages/parchment/lib/src/codecs/markdown.dart @@ -530,6 +530,8 @@ class _ParchmentMarkdownEncoder extends Converter { _writeUnderlineTag(buffer, close: close); } else if (strict) { throw ArgumentError('Cannot handle $attribute'); + } else { + _writeObjectTag(buffer); } } @@ -542,7 +544,7 @@ class _ParchmentMarkdownEncoder extends Converter { } void _writeUnderlineTag(StringBuffer buffer, {bool close = false}) { - if (false) { + if (close) { buffer.write(''); } else { buffer.write(''); @@ -595,4 +597,8 @@ class _ParchmentMarkdownEncoder extends Converter { void _writeHorizontalLineTag(StringBuffer buffer) { buffer.write('---'); } + + void _writeObjectTag(StringBuffer buffer) { + buffer.write('[object]'); + } } From 1612a929edb0458e05c014866b6d5c41782b96fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chiotti?= <44336112+maelchiotti@users.noreply.github.com> Date: Sun, 13 Oct 2024 18:40:56 +0200 Subject: [PATCH 9/9] tests: add simple encoding test --- packages/parchment/lib/codecs.dart | 8 ++++++-- packages/parchment/test/codecs/markdown_test.dart | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/parchment/lib/codecs.dart b/packages/parchment/lib/codecs.dart index 41be299d..73df2219 100644 --- a/packages/parchment/lib/codecs.dart +++ b/packages/parchment/lib/codecs.dart @@ -1,14 +1,18 @@ /// Provides codecs to convert Parchment documents to other formats. library parchment.codecs; -import 'src/codecs/markdown.dart'; import 'src/codecs/html.dart'; +import 'src/codecs/markdown.dart'; -export 'src/codecs/markdown.dart'; export 'src/codecs/html.dart'; +export 'src/codecs/markdown.dart'; /// Markdown codec for Parchment documents. const parchmentMarkdown = ParchmentMarkdownCodec(); +/// Not strict markdown codec for Parchment documents. +const parchmentMarkdownNotStrict = + ParchmentMarkdownCodec(strictEncoding: false); + /// HTML codec for Parchment documents. const parchmentHtml = ParchmentHtmlCodec(); diff --git a/packages/parchment/test/codecs/markdown_test.dart b/packages/parchment/test/codecs/markdown_test.dart index f0f52820..7f1956cc 100644 --- a/packages/parchment/test/codecs/markdown_test.dart +++ b/packages/parchment/test/codecs/markdown_test.dart @@ -144,6 +144,20 @@ void main() { runFor('~~strike through~~\n\n', true); }); + test('underline', () { + final delta = Delta() + ..insert('an ') + ..insert('underlined', ParchmentAttribute.underline.toJson()) + ..insert(' text') + ..insert('\n'); + + final result = + parchmentMarkdownNotStrict.encode(ParchmentDocument.fromDelta(delta)); + final expected = 'an underlined text\n\n'; + + expect(result, expected); + }); + test('intersecting inline styles', () { final markdown = 'This **house _is a_ circus**\n\n'; final document = parchmentMarkdown.decode(markdown);