Files
GitJournal/lib/core/link.dart
Vishesh Handa c7737ffd13 Port LinksLoader to null safety
With this I'm not accepting any of the Link's field to be an empty
string. In that case it's better for it to be null, as the compiler will
force me to deal with an empty value.
2021-04-15 17:22:15 +02:00

172 lines
3.8 KiB
Dart

import 'package:markdown/markdown.dart' as md;
// FIXME: This should be split into 2 classes, that way it would be easier
// to access to members with null safety
class Link {
String? publicTerm;
String? filePath;
String? headingID;
String? alt;
String? wikiTerm;
Link({
required this.publicTerm,
required this.filePath,
this.headingID,
this.alt,
}) {
if (publicTerm?.isEmpty == true) {
publicTerm = null;
}
if (headingID?.isEmpty == true) {
headingID = null;
}
if (alt?.isEmpty == true) {
alt = null;
}
}
Link.wiki(this.wikiTerm);
bool get isWikiLink => wikiTerm != null;
@override
int get hashCode => filePath.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Link &&
runtimeType == other.runtimeType &&
filePath == other.filePath &&
publicTerm == other.publicTerm &&
wikiTerm == other.wikiTerm &&
headingID == other.headingID &&
alt == other.alt;
@override
String toString() {
return wikiTerm != null
? 'WikiLink($wikiTerm)'
: 'Link{publicTerm: $publicTerm, filePath: $filePath, headingID: $headingID, alt: $alt}';
}
}
class LinkExtractor implements md.NodeVisitor {
final String filePath;
List<Link> links = [];
LinkExtractor(this.filePath);
@override
bool visitElementBefore(md.Element element) {
return true;
}
@override
void visitText(md.Text text) {}
@override
void visitElementAfter(md.Element el) {
final String tag = el.tag;
if (tag == 'a') {
var type = el.attributes['type'] ?? "";
if (type == "wiki") {
var term = el.attributes['term'];
var link = Link.wiki(term);
links.add(link);
return;
}
var alt = el.attributes['title'];
var title = _getText(el.children);
var url = el.attributes['href'];
if (url == null || isExternalLink(url)) {
return;
}
if (url.startsWith('#') || url.startsWith('//')) {
// FIXME: The heading ID seems incorrect
var link = Link(
publicTerm: title,
filePath: filePath,
alt: alt,
headingID: url,
);
links.add(link);
return;
}
var link = Link(publicTerm: title, filePath: url, alt: alt);
links.add(link);
return;
}
}
static bool isExternalLink(String url) {
return url.startsWith(RegExp(r'[A-Za-z]{2,5}:\/\/'));
}
List<Link> visit(List<md.Node> nodes) {
for (final node in nodes) {
node.accept(this);
}
return links;
}
String _getText(List<md.Node>? nodes) {
if (nodes == null) {
return "";
}
var text = "";
for (final node in nodes) {
if (node is md.Text) {
text += node.text;
} else if (node is md.Element) {
text += _getText(node.children);
}
}
return text;
}
}
/// Parse [[term]]
class WikiLinkSyntax extends md.InlineSyntax {
static final String _pattern = r'\[\[([^\[\]]+)\]\]';
// In Obsidian style, the link is like [[fileToLinkTo|display text]]
final bool obsidianStyle;
WikiLinkSyntax({this.obsidianStyle = true}) : super(_pattern);
@override
bool onMatch(md.InlineParser parser, Match match) {
var term = match[1]!.trim();
var displayText = term;
if (term.contains('|')) {
var s = term.split('|');
if (obsidianStyle) {
term = s[0].trimRight();
displayText = s[1].trimLeft();
} else {
displayText = s[0].trimRight();
term = s[1].trimLeft();
}
}
var el = md.Element('a', [md.Text(displayText)]);
el.attributes['type'] = 'wiki';
el.attributes['href'] = '[[$term]]';
el.attributes['term'] = term;
parser.addNode(el);
return true;
}
}