blob: 925d4f0af1a0c7e577319194ce9576f1df030e26 [file] [log] [blame]
// Copyright (c) 2020, 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 'dart:async';
import 'dart:js_interop';
import 'package:markdown/markdown.dart' as md;
import 'package:web/web.dart';
import 'highlight.dart';
final markdownInput =
document.querySelector('#markdown') as HTMLTextAreaElement;
final htmlDiv = document.querySelector('#html') as HTMLDivElement;
final versionSpan = document.querySelector('.version') as HTMLSpanElement;
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_...''';
// Flavor support.
final basicRadio = document.querySelector('#basic-radio') as HTMLElement;
final commonmarkRadio =
document.querySelector('#commonmark-radio') as HTMLElement;
final gfmRadio = document.querySelector('#gfm-radio') as HTMLElement;
md.ExtensionSet? extensionSet;
final extensionSets = {
'basic-radio': md.ExtensionSet.none,
'commonmark-radio': md.ExtensionSet.commonMark,
'gfm-radio': md.ExtensionSet.gitHubWeb,
};
void main() {
versionSpan.text = 'v${md.version}';
markdownInput.onKeyUp.listen(_renderMarkdown);
final savedMarkdown = window.localStorage['markdown'];
if (savedMarkdown != null &&
savedMarkdown.isNotEmpty &&
savedMarkdown != introText) {
markdownInput.value = savedMarkdown;
markdownInput.focus();
_renderMarkdown();
} else {
_typeItOut(introText, 82);
}
// GitHub is the default extension set.
gfmRadio.attributes.getNamedItem('checked')?.value = '';
gfmRadio.querySelector('.glyph')!.text = 'radio_button_checked';
extensionSet = extensionSets[gfmRadio.id];
_renderMarkdown();
basicRadio.onClick.listen(_switchFlavor);
commonmarkRadio.onClick.listen(_switchFlavor);
gfmRadio.onClick.listen(_switchFlavor);
}
void _renderMarkdown([Event? event]) {
final markdown = markdownInput.value;
htmlDiv.innerHTML = md.markdownToHtml(markdown, extensionSet: extensionSet);
for (final block in htmlDiv.querySelectorAll('pre code').items) {
try {
highlightElement(block);
} catch (e) {
console.error('Error highlighting markdown:'.toJS);
console.error(e.toString().toJS);
}
}
if (event != null) {
// Not simulated typing. Store it.
window.localStorage['markdown'] = markdown;
}
}
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 _switchFlavor(Event e) {
final target = e.currentTarget as HTMLElement;
if (target.attributes.getNamedItem('checked') == null) {
if (basicRadio != target) {
basicRadio.attributes.safeRemove('checked');
basicRadio.querySelector('.glyph')!.text = 'radio_button_unchecked';
}
if (commonmarkRadio != target) {
commonmarkRadio.attributes.safeRemove('checked');
commonmarkRadio.querySelector('.glyph')!.text = 'radio_button_unchecked';
}
if (gfmRadio != target) {
gfmRadio.attributes.safeRemove('checked');
gfmRadio.querySelector('.glyph')!.text = 'radio_button_unchecked';
}
target.attributes.getNamedItem('checked')?.value = '';
target.querySelector('.glyph')!.text = 'radio_button_checked';
extensionSet = extensionSets[target.id];
_renderMarkdown();
}
}
extension on NodeList {
List<Node> get items => [
for (var i = 0; i < length; i++) item(i)!,
];
}
extension on NamedNodeMap {
void safeRemove(String qualifiedName) {
if (getNamedItem(qualifiedName) != null) removeNamedItem(qualifiedName);
}
}