Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basis for fenced code block transformations, either synchronous or asynchronous #433

Open
wants to merge 24 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
fe676b9
init version of with possible fenced code block transformations.
timmaffett May 9, 2022
b0746d2
transformable fenced code blocks
timmaffett May 9, 2022
991c0b0
attempted version
timmaffett May 18, 2022
19d37aa
make visit text use .textContent instead of .text so it can be overri…
timmaffett May 18, 2022
4e2fe41
working well before cleanup
timmaffett May 18, 2022
a3fd730
more cleanup, kroki.dart package used to illustrate async diagram ren…
timmaffett May 18, 2022
5edf665
samples moved into kroki package
timmaffett May 19, 2022
b9c8364
clean up example
timmaffett May 19, 2022
ea3c26d
finish comment cleanup
timmaffett May 19, 2022
e076834
finish comment cleanup
timmaffett May 19, 2022
208acad
add tests for synchronous and async transformable fenced code block s…
timmaffett May 20, 2022
dd6f3e3
remove obsolete parameters
timmaffett May 20, 2022
6a3cac7
don't add language class is no fenced code block type
timmaffett May 20, 2022
992ab0b
add more test of tranforming fenced code blocks, dart format
timmaffett May 20, 2022
7d73a5d
Merge branch 'dart-lang:master' into code_block_transformers
timmaffett Jun 7, 2022
574f00a
comment cleanup, some variable declaration simplifications
timmaffett Jun 8, 2022
3d97b08
kroki to 0.0.3 which will accept CI sdk of 2.12.0
timmaffett Jun 8, 2022
e667a4d
bumped sdk min version to 2.14.0 in attempt to get CI to work
timmaffett Jun 8, 2022
ef5b6df
move min sdk back to 2.12.0, changing did not affect CI version
timmaffett Jun 8, 2022
7b742c3
kroki to 0.0.4 which uses crypto 3.0.1 which uses sdk 2.12.0 and may …
timmaffett Jun 8, 2022
ec2873a
Merge branch 'dart-lang:master' into code_block_transformers
timmaffett Jun 9, 2022
2f74bbc
Merge branch 'master' into code_block_transformers
timmaffett Jul 20, 2022
b3fb24e
sort exports
timmaffett Jul 20, 2022
65677dc
Merge branch 'master' into code_block_transformers
timmaffett May 30, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 81 additions & 32 deletions example/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,34 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';
import 'dart:html';

import 'package:kroki/kroki.dart';
import 'package:markdown/markdown.dart' as md;

import 'highlight.dart';
import 'kroki_markdown_ext.dart';

final markdownInput = querySelector('#markdown') as TextAreaElement;
final htmlDiv = querySelector('#html') as DivElement;
final versionSpan = querySelector('.version') as SpanElement;

final nullSanitizer = NullTreeSanitizer();
const typing = Duration(milliseconds: 150);
const introText = '''Markdown is the **best**!

* It has lists.
* It has [links](https://dart.dev).
* It has...
```dart
void sourceCode() {}
```
* ...and _so much more_...''';
String sampleDiagramsText = buildSamplesText(1, 10);
bool useLocalStorageVersion = false;

// Flavor support.
final basicRadio = querySelector('#basic-radio') as HtmlElement;
final commonmarkRadio = querySelector('#commonmark-radio') as HtmlElement;
final gfmRadio = querySelector('#gfm-radio') as HtmlElement;

// Samples group radio buttons.
final group1Radio = querySelector('#group1-radio') as HtmlElement;
final group2Radio = querySelector('#group2-radio') as HtmlElement;
final group3Radio = querySelector('#group3-radio') as HtmlElement;

// Extension set choices.
md.ExtensionSet? extensionSet;

final extensionSets = {
Expand All @@ -43,14 +44,15 @@ void main() {

final savedMarkdown = window.localStorage['markdown'];

if (savedMarkdown != null &&
if (useLocalStorageVersion &&
savedMarkdown != null &&
savedMarkdown.isNotEmpty &&
savedMarkdown != introText) {
savedMarkdown != sampleDiagramsText) {
markdownInput.value = savedMarkdown;
markdownInput.focus();
_renderMarkdown();
} else {
_typeItOut(introText, 82);
_setMarkdown(sampleDiagramsText);
}

// GitHub is the default extension set.
Expand All @@ -62,13 +64,25 @@ void main() {
basicRadio.onClick.listen(_switchFlavor);
commonmarkRadio.onClick.listen(_switchFlavor);
gfmRadio.onClick.listen(_switchFlavor);

// Set default samples group.
group1Radio.attributes['checked'] = '';
group1Radio.querySelector('.glyph')!.text = 'radio_button_checked';

group1Radio.onClick.listen(_switchExamples);
group2Radio.onClick.listen(_switchExamples);
group3Radio.onClick.listen(_switchExamples);
}

void _renderMarkdown([Event? event]) {
void _renderMarkdown([Event? event]) async {
final markdown = markdownInput.value!;

final outputHtml = await md.markdownToHtmlWithAsyncTransforms(markdown,
blockSyntaxes: [diagramTransformingFencedCodeBlock],
extensionSet: extensionSet);

htmlDiv.setInnerHtml(
md.markdownToHtml(markdown, extensionSet: extensionSet),
outputHtml,
treeSanitizer: nullSanitizer,
);

Expand All @@ -87,23 +101,10 @@ void _renderMarkdown([Event? event]) {
}
}

void _typeItOut(String msg, int pos) {
late Timer timer;
markdownInput.onKeyUp.listen((_) {
timer.cancel();
});
void addCharacter() {
if (pos > msg.length) {
return;
}
markdownInput.value = msg.substring(0, pos);
markdownInput.focus();
_renderMarkdown();
pos++;
timer = Timer(typing, addCharacter);
}

timer = Timer(typing, addCharacter);
void _setMarkdown(String newMarkdown) {
markdownInput.value = newMarkdown;
markdownInput.focus();
_renderMarkdown();
}

void _switchFlavor(Event e) {
Expand All @@ -129,7 +130,55 @@ void _switchFlavor(Event e) {
}
}

void _switchExamples(Event e) {
final target = e.currentTarget as HtmlElement;
if (!target.attributes.containsKey('checked')) {
int groupStart = 0;
int groupLen = 10;
if (group1Radio != target) {
group1Radio.attributes.remove('checked');
group1Radio.querySelector('.glyph')!.text = 'radio_button_unchecked';
}
if (group2Radio != target) {
group2Radio.attributes.remove('checked');
group2Radio.querySelector('.glyph')!.text = 'radio_button_unchecked';
} else {
groupStart = 10;
}
if (group3Radio != target) {
group3Radio.attributes.remove('checked');
group3Radio.querySelector('.glyph')!.text = 'radio_button_unchecked';
} else {
groupStart = 20;
groupLen = 15;
}

target.attributes['checked'] = 'true';
target.querySelector('.glyph')!.text = 'radio_button_checked';
sampleDiagramsText = buildSamplesText(groupStart, groupLen);
_setMarkdown(sampleDiagramsText);
}
}

class NullTreeSanitizer implements NodeTreeSanitizer {
@override
void sanitizeTree(Node node) {}
}

String buildSamplesText(int startIndex, int numTests) {
final List<String> sampleTextBlocks = [];

for (var i = startIndex; i < (startIndex + numTests); i++) {
if (i >= KrokiSampleDiagrams.samples.length) break;
final sample = KrokiSampleDiagrams.samples[i];
sampleTextBlocks.add('''
## [${sample.name}](${sample.url})
----------------------------------
```${sample.diagramType}
${sample.diagramSource}
```
''');
}

return sampleTextBlocks.join('\n');
}
16 changes: 15 additions & 1 deletion example/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/highlight.min.js" integrity="sha512-yUUc0qWm2rhM7X0EFe82LNnv2moqArj5nro/w1bi05A09hRVeIZbN6jlMoyu0+4I/Bu4Ck/85JQIU82T82M28w==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/languages/dart.min.js" integrity="sha512-14QR6tzX5xTNeMJKXzSK+xCquDvtNEr1jM5NlKy/149BBY50Kv70qqxHtzo6zClbtc1gIG7G0CGWXuMgPIMt0g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script defer src="app.dart.js"></script>
<title>Dart Markdown Live Editor</title>
<title>Dart Markdown Live Editor using Krokio.io to render to static SVGs</title>
<style>
.gfm-color_chip span {
display: inline-block;
Expand Down Expand Up @@ -57,6 +57,20 @@ <h2>Dart Markdown Live Editor</h2>
<div class="card">
<div class="toolbar">
<h2>Markdown</h2>
<span id="examplesbar" style="margin-left: auto;float:right;">Examples Group:&nbsp;
<div class="radio" id="group1-radio">
<i class="glyph">radio_button_unchecked</i>
#1
</div>
<div class="radio" id="group2-radio">
<i class="glyph">radio_button_unchecked</i>
#2
</div>
<div class="radio" id="group3-radio">
<i class="glyph">radio_button_unchecked</i>
#3
</div>
</span>
</div>
<div style="padding: 8px; display: flex; height: 100%;">
<textarea id="markdown"></textarea>
Expand Down
38 changes: 38 additions & 0 deletions example/kroki_markdown_ext.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import 'package:kroki/kroki.dart';
import 'package:markdown/markdown.dart' as markdown;

/// Use public kroki installation, but this could be custom installation
/// of our own using a subset of diagram endpoints that we choose to support,
/// running on our own server.
final Kroki kroki =
Kroki(krokiApiUrl: 'https://kroki.io/', cacheRequests: true);

class DiagramTransfomer extends markdown.CodeBlockTransformer {
@override
markdown.Node? transformCodeBlock(
String codeBlockType, String rawCodeBlock, markdown.BlockParser parser) {
final markdown.AsyncText asyncText = markdown.AsyncText(
kroki.convertDiagram(codeBlockType, rawCodeBlock), parser,
uncompletedFutureTextValue:
rawCodeBlock // Fallback to showing original diagram source.
);
return asyncText;
}

/// Set supported code block types to all those that Kroki package supports.
DiagramTransfomer()
: super(handledCodeBlockTypes: KrokiDiagramEndpoints.supportedEndpoints);
}

final diagramTransformingFencedCodeBlock =
markdown.TransformableFencedCodeBlockSyntax([DiagramTransfomer()]);

/// Example code to invoke markdown package to convert markdown source
/// which would have diagrams converted to svgs:
///
///```dart
/// final finalHtml = await markdown.markdownToHtmlWithAsyncTransforms(
/// markdownSource,
/// blockSyntaxes: [diagramTransformingFencedCodeBlock],
/// extensionSet: markdown.ExtensionSet.gitHubWeb);
///```
4 changes: 4 additions & 0 deletions example/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ i.glyph {
color: #22d3c5;
}

#examplesbar .radio[checked] i.glyph {
color: #6622d3;
}

i.glyph.big {
font-size: 32px;
height: 32px;
Expand Down
4 changes: 4 additions & 0 deletions lib/markdown.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ library markdown;
import 'src/version.dart';

export 'src/ast.dart';
export 'src/async_html_renderer.dart';
export 'src/async_transforms.dart';
export 'src/block_parser.dart';
export 'src/block_syntaxes/block_syntax.dart';
export 'src/block_syntaxes/blockquote_syntax.dart';
Expand All @@ -57,8 +59,10 @@ export 'src/block_syntaxes/paragraph_syntax.dart';
export 'src/block_syntaxes/setext_header_syntax.dart';
export 'src/block_syntaxes/setext_header_with_id_syntax.dart';
export 'src/block_syntaxes/table_syntax.dart';
export 'src/block_syntaxes/transformable_fenced_code_block_syntax.dart';
export 'src/block_syntaxes/unordered_list_syntax.dart';
export 'src/block_syntaxes/unordered_list_with_checkbox_syntax.dart';
export 'src/code_block_transformer.dart';
export 'src/document.dart';
export 'src/emojis.dart';
export 'src/extension_set.dart';
Expand Down
43 changes: 43 additions & 0 deletions lib/src/async_html_renderer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'ast.dart';
import 'async_transforms.dart';
import 'block_syntaxes/block_syntax.dart';
import 'extension_set.dart';
import 'html_renderer.dart';
import 'inline_syntaxes/inline_syntax.dart';

/// Converts the given string of Markdown to HTML.
Future<String> markdownToHtmlWithAsyncTransforms(
String markdown, {
Iterable<BlockSyntax> blockSyntaxes = const [],
Iterable<InlineSyntax> inlineSyntaxes = const [],
ExtensionSet? extensionSet,
Resolver? linkResolver,
Resolver? imageLinkResolver,
bool encodeHtml = true,
bool withDefaultBlockSyntaxes = true,
bool withDefaultInlineSyntaxes = true,
}) async {
final asyncDocument = AsyncDocument(
blockSyntaxes: blockSyntaxes,
inlineSyntaxes: inlineSyntaxes,
extensionSet: extensionSet,
linkResolver: linkResolver,
imageLinkResolver: imageLinkResolver,
encodeHtml: encodeHtml,
withDefaultBlockSyntaxes: withDefaultBlockSyntaxes,
withDefaultInlineSyntaxes: withDefaultInlineSyntaxes,
);

// Replace windows line endings with unix line endings, and split.
final lines = markdown.replaceAll('\r\n', '\n').split('\n');

final nodes = asyncDocument.parseLines(lines);

await asyncDocument.waitForCompletion();

return '${renderToHtml(nodes)}\n';
}
Loading