From bcf583c9ec732eae89c2dadc1bcc0e9395142cdf Mon Sep 17 00:00:00 2001 From: cotw-fabier Date: Mon, 24 Jun 2024 18:12:43 -0400 Subject: [PATCH] Updated Examples with sample of new changes in action along with comment instructions --- .../parchment/example/encoder_example.dart | 118 ++++++++++++++++++ .../lib/src/codecs/codec_extensions.dart | 7 +- packages/parchment/lib/src/codecs/html.dart | 16 +-- .../parchment/lib/src/codecs/markdown.dart | 38 +++--- 4 files changed, 147 insertions(+), 32 deletions(-) create mode 100644 packages/parchment/example/encoder_example.dart diff --git a/packages/parchment/example/encoder_example.dart b/packages/parchment/example/encoder_example.dart new file mode 100644 index 00000000..44395df7 --- /dev/null +++ b/packages/parchment/example/encoder_example.dart @@ -0,0 +1,118 @@ +import 'package:parchment/codecs.dart'; +import 'package:parchment/parchment.dart'; +import 'package:parchment/src/codecs/codec_extensions.dart'; +import 'package:parchment_delta/parchment_delta.dart'; + +void main() { + // We're going to start by creating a new blank document + final doc = ParchmentDocument(); + + // Since this is an example of building a custom embed. We're going to define a custom embed object. + // "Youtube" refers to the name of the embed object + // "inline" will communicate if this embed is inline with other content, or if it lives by itself on its own line. + // Embeds take up one character but are encoded as a simple object with Map data. + // You can see the data as the next argument in the constructor. + // Data can have literally any data you want. + final url = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'; + final thumbUrl = 'https://img.youtube.com/vi/dQw4w9WgXcQ/0.jpg'; + + // We're going to do both an inline and a block embed. They are essentially the same except the inline property. + // Inline Block Embed + final youtubeInlineEmbedDelta = { + "_type": "youtube", + "_inline": true, + "url": url, + "title": "Read the Url Before Clicking", + "language": "en", + "thumbUrl": thumbUrl + }; + + // Block Embed + final youtubeBlockEmbedDelta = { + "_type": "youtube", + "_inline": false, + "url": url, + "title": "Read the Url Before Clicking", + "language": "en", + "thumbUrl": thumbUrl + }; + + // Lets create new Delta to insert content into our document. + final newDelta = Delta() + ..insert( + 'Lets add in some examples of custom embed blocks which we\'ll implement custom encoders to encode the result.') + ..insert("\n") + ..insert("Lets Start with a simple inline block: ") + ..insert(youtubeInlineEmbedDelta) + ..insert("\n") + ..insert("Now lets add a block embed: \n") + ..insert(youtubeBlockEmbedDelta); + + // Since we know our changes are progormatically generated they don't need to be run through Heuristics and Autoformatting. + // So we are going to use the compose command which bypasses any of Fleather's additional logic to keep content consistent and clean. + // This is useful for programatically generated content but you should use another command if the content includes any user input so it properly formats. + // Using ChangeSource.local because these changes originated programmatically on our machine. + doc.compose(newDelta, ChangeSource.local); + + // This is where some of the magic happens. Lets define a custom encoder so we can format our youtube embed for export from fleather. + // If you are just saving to the database then using jsonEncode(doc) would be enough and no additional work needed. + // But if you want to make use of fleather's excellent HTML and Markdown encoders then we need to take an additional step. + + // Lets start with markdown since it is simpler. + final markdownYouTubeEncoder = EncodeExtension( + codecType: CodecExtensionType + .markdown, // We use this so we can pass all encoders to the converter and the converter can smart select the correct encoders it would like to use. + blockType: + 'youtube', // We're matching against the type of embed. "youtube" was defined above as the first param of our EmbeddableObject. + encode: (EmbeddableObject embeddable) { + return "[![${embeddable.data['title']}](${embeddable.data['thumbUrl']})](${embeddable.data['url']})"; + }); // This function takes in an embeddable object and returns a string which you can use with markdown. + + // A few important things to note about the encode function. + // 1.) The encode function left out the language. We can store information in the embed object which we don't want to display. + // 2.) You have access to all the fields of the embed by using embeddable.data['field_name'] + + // Lets trying making an encoder for HTML now. + final htmlYouTubeEncoder = EncodeExtension( + codecType: CodecExtensionType.html, // We change to HTML here + blockType: + 'youtube', // Still matching against Youtube since we're encoding the same type of block embed. + encode: (EmbeddableObject embeddable) { + return """
+ + ${embeddable.data['title']} + ${embeddable.data['title']} + +
+ + """; + }); + + // For the HTML output we set the content to display as inline-block. This is because the encoder runs both as a block and inline elemnt. + // Fleather will still wrap block embeds in

tags, so displaying as inline-block should work for both. + + // Now that we have two encoders for our HTML block, Markdown and HTML, lets try to export out document has HTML and Markdown. + final encoderList = [markdownYouTubeEncoder, htmlYouTubeEncoder]; + + // Lets encode our document to HTML and Markdown + // Notice how we can just pass our list to the codec without any additional work. So define all your encoders and just pass them along when encoding. + final htmlOutput = ParchmentHtmlCodec(extensions: encoderList).encode(doc); + final markdownOutput = + ParchmentMarkdownCodec(extensions: encoderList).encode(doc); + + // Lets print out our results. + print("HTML Output:"); + print(htmlOutput); + print("\n\n"); + print("Markdown Output:"); + print(markdownOutput); + + // Congrats! You can now make all manner of awesome custom embeds and work with them like any other text. + // Using fleather's fabulous embed rendering engine in the editor you can call functions, update widgets + // and do all sorts of logic within your embed functions. Then when you're done, call these export functions + // with your custom encoders and you're good to go! + + // Dispose resources allocated by this document, e.g. closes "changes" stream. + // After document is closed it cannot be modified. + doc.close(); +} diff --git a/packages/parchment/lib/src/codecs/codec_extensions.dart b/packages/parchment/lib/src/codecs/codec_extensions.dart index a07a7edd..be56520b 100644 --- a/packages/parchment/lib/src/codecs/codec_extensions.dart +++ b/packages/parchment/lib/src/codecs/codec_extensions.dart @@ -1,11 +1,10 @@ // This document contains functions for extending the default Encoders and Decoders which come with Fleather // This allows you to write custom extensions which are called when running the encode and decode functions. -// By including a type you allow the extension to be scoped to availble options -// for codecs available. -// In this case that would be Markdown and HTML. +// By including a type you allow the extension to be scoped to availble options (Markdown, HTML). +// So you can just build a big pile of extensions if you want and just add them in to every instance of the encoder/decoder. // Custom Encoder and Decoder functions run BEFORE the default encoder and decoder functions. -// This means you can override normal behavior of the encoder if desired. +// This means you can override normal behavior of the default embed encoder if desired (really just for HR and image tags at this point). import 'package:html/dom.dart'; import 'package:parchment/src/document/embeds.dart'; diff --git a/packages/parchment/lib/src/codecs/html.dart b/packages/parchment/lib/src/codecs/html.dart index 4f84577c..519e7604 100644 --- a/packages/parchment/lib/src/codecs/html.dart +++ b/packages/parchment/lib/src/codecs/html.dart @@ -492,14 +492,16 @@ class _ParchmentHtmlEncoder extends Converter { if (op.data is Map) { final data = op.data as Map; final embeddable = EmbeddableObject.fromJson(data); - if (embeddable is BlockEmbed) { - // We're going to loop through our custom encoder extensions here to see if we can encode this block. - for (final EncodeExtension extension in extensions ?? []) { - if (extension.canEncode(CodecExtensionType.html, embeddable.type)) { - buffer.write(extension.encode(embeddable)); - return; - } + + // We're going to loop through our custom encoder extensions here to see if we can encode this block. + for (final EncodeExtension extension in extensions ?? []) { + if (extension.canEncode(CodecExtensionType.html, embeddable.type)) { + buffer.write(extension.encode(embeddable)); + return; } + } + + if (embeddable is BlockEmbed) { if (embeddable.type == 'hr') { buffer.write('
'); return; diff --git a/packages/parchment/lib/src/codecs/markdown.dart b/packages/parchment/lib/src/codecs/markdown.dart index 6f0a643c..c067fe24 100644 --- a/packages/parchment/lib/src/codecs/markdown.dart +++ b/packages/parchment/lib/src/codecs/markdown.dart @@ -410,25 +410,6 @@ class _ParchmentMarkdownEncoder extends Converter { ParchmentAttribute? currentBlockAttribute; void handleLine(LineNode node) { - // We're going to load our custom extensions here. - // Loop through available extensions and apply them if applicable. - if (node.hasBlockEmbed) { - //inspect(node); - for (final EncodeExtension extension in extensions ?? []) { - // Convert to embednode and check if we can encode it. - final blockEmbed = node.children.first as EmbedNode; - if (extension.canEncode( - CodecExtensionType.markdown, blockEmbed.value.type)) { - // Pass the embeddable object to the extension encode function - // Return a string which writes to the encode buffer. - buffer.write(extension.encode(blockEmbed.value)); - // Return this function since it runs line-by-line. - // No need to keep checking for additional extensions since we already encoded the results. - return; - } - } - } - for (final attr in node.style.lineAttributes) { if (attr.key == ParchmentAttribute.block.key) { if (currentBlockAttribute != attr) { @@ -443,8 +424,23 @@ class _ParchmentMarkdownEncoder extends Converter { } for (final textNode in node.children) { - handleText(lineBuffer, textNode as TextNode, currentInlineStyle); - currentInlineStyle = textNode.style; + if (textNode is TextNode) { + handleText(lineBuffer, textNode as TextNode, currentInlineStyle); + currentInlineStyle = textNode.style; + } else if (textNode is EmbedNode) { + // Import custom extensions for block and inline embeds. + // If there is an extension which matches the extension type and the EmbedBlock type + // then we will run the encode function and write the output to the buffer. + // Otherwise we'll drop it silently. + for (final EncodeExtension extension in extensions ?? []) { + if (extension.canEncode( + CodecExtensionType.markdown, textNode.value.type)) { + // Pass the embeddable object to the extension encode function + // Return a string which writes to the encode buffer. + lineBuffer.write(extension.encode(textNode.value)); + } + } + } } handleText(lineBuffer, TextNode(), currentInlineStyle);