majro update

This commit is contained in:
Fabian Freund
2025-02-28 14:30:49 +01:00
parent a8957f6cf8
commit 3c4d479bd9
181 changed files with 7090 additions and 5299 deletions
-96
View File
@@ -1,96 +0,0 @@
import 'package:html/dom.dart';
import 'package:http/http.dart' as http;
class FeedFinder {
final Uri url;
final Document document;
late final String _base;
FeedFinder({required this.url, required this.document}) {
final uri = url.removeFragment();
_base = '${uri.scheme}://${uri.authority}';
}
Future<Set<String>> _verifyCandidates(Set<String> candidates) async {
final results = <String>{};
final client = http.Client();
try {
for (final candidate in candidates) {
try {
await client.get(Uri.parse(candidate));
} catch (e) {
continue;
}
results.add(candidate);
}
} finally {
client.close();
}
return results;
}
void _parseBody(Set<String> candidates) {
for (final a in document.querySelectorAll('a')) {
var href = a.attributes['href'];
if (href != null) {
if (href.contains('rss') ||
href.contains('xml') ||
href.contains('feed')) {
// Fix relative URLs
href = href.startsWith('/') ? _base + href : href;
href = href.endsWith('/') ? href.substring(0, href.length - 2) : href;
// Fix naked URLs
href = !href.startsWith('http') ? '$_base/$href' : href;
candidates.add(href);
}
}
}
}
void _parseHead(Set<String> candidates) {
for (final link in document.querySelectorAll("link[rel='alternate']")) {
final type = link.attributes['type'];
if (type != null) {
if (type.contains('rss') || type.contains('xml')) {
var href = link.attributes['href'];
if (href != null) {
// Fix relative URLs
href = href.startsWith('/') ? _base + href : href;
candidates.add(href);
}
}
}
}
}
Future<Set<String>> parse({
bool parseHead = true,
bool parseBody = true,
bool verifyCandidates = true,
}) async {
final candidates = <String>{};
// Look for feed candidates in head
if (parseHead) {
_parseHead(candidates);
}
// Look for feed candidates in body
if (parseBody) {
_parseBody(candidates);
}
// Verify candidates
if (verifyCandidates) {
return _verifyCandidates(candidates);
}
return candidates;
}
}
@@ -0,0 +1,32 @@
import 'package:lensai/extensions/nullable.dart';
import 'package:markdown/markdown.dart';
import 'package:path/path.dart' as p;
List<Uri> extractImagesFromMarkdown(String markdownText) {
final imageUrls = <Uri>[];
final nodes = Document().parse(markdownText);
void findImages(List<Node> nodes) {
for (final node in nodes) {
if (node is Element && node.tag == 'img') {
final url = node.attributes['src'];
if (url.mapNotNull((url) => Uri.tryParse(url)) case final Uri url) {
if (p.extension(url.path)
case '.png' || '.jpg' || '.jpeg' || '.webp') {
imageUrls.add(url);
}
}
}
if (node is Element && node.children != null) {
findImages(node.children!);
}
}
}
findImages(nodes);
return imageUrls;
}
+55 -13
View File
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:lensai/extensions/nullable.dart';
import 'package:url_launcher/url_launcher.dart';
void showErrorMessage(BuildContext context, String message) {
@@ -9,6 +10,60 @@ void showErrorMessage(BuildContext context, String message) {
),
backgroundColor: Theme.of(context).colorScheme.onError,
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
void showTabBackButtonMessage(BuildContext context, int tabCount) {
final snackbar = SnackBar(
content:
(tabCount > 1)
? const Text('Please click BACK again to close current tab')
: const Text('Please click BACK again to exit app'),
);
ScaffoldMessenger.of(context)
..clearSnackBars()
..showSnackBar(snackbar);
}
void showTabOpenedMessage(
BuildContext context, {
String? tabName,
void Function()? onShow,
}) {
final message = switch (tabName.whenNotEmpty) {
String() => "New tab '$tabName' opened in background",
null => 'New tab opened in background',
};
final snackBar = SnackBar(
content: Text(message),
action: onShow.mapNotNull(
(onPressed) => SnackBarAction(label: 'Show', onPressed: onPressed),
),
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
void showTabSwitchMessage(
BuildContext context, {
String? tabName,
void Function()? onSwitch,
}) {
final message = switch (tabName.whenNotEmpty) {
String() => "New tab '$tabName' opened",
null => 'New tab opened',
};
final snackBar = SnackBar(
content: Text(message),
action: onSwitch.mapNotNull(
(onPressed) => SnackBarAction(label: 'Switch', onPressed: onPressed),
),
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
@@ -29,16 +84,3 @@ Future<void> launchUrlFeedback(
}
}
}
void showTabBackButtonMessage(BuildContext context, int tabCount) {
ScaffoldMessenger.of(context)
..clearSnackBars()
..showSnackBar(
SnackBar(
content:
(tabCount > 1)
? const Text('Please click BACK again to close current tab')
: const Text('Please click BACK again to exit app'),
),
);
}