diff --git a/app/lib/widgets/item/details/item_details_rss.dart b/app/lib/widgets/item/details/item_details_rss.dart
index 4aa2268..bede5ab 100644
--- a/app/lib/widgets/item/details/item_details_rss.dart
+++ b/app/lib/widgets/item/details/item_details_rss.dart
@@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
+import 'package:html/parser.dart' show parse;
+
import 'package:feeddeck/models/item.dart';
import 'package:feeddeck/models/source.dart';
import 'package:feeddeck/widgets/item/details/utils/item_description.dart';
@@ -17,8 +19,26 @@ class ItemDetailsRSS extends StatelessWidget {
final FDItem item;
final FDSource source;
+ /// [_buildImage] renders the [item.media] when the [shouldBeRendered] is
+ /// `true`. If it is `false` an empty container is returned.
+ Widget _buildImage(bool shouldBeRendered) {
+ if (!shouldBeRendered) {
+ return Container();
+ }
+
+ return ItemMedia(
+ itemMedia: item.media,
+ );
+ }
+
@override
Widget build(BuildContext context) {
+ /// Check if the description of the RSS feed contains an image. If this is
+ /// the case we do not render the image from the [item.media] because the
+ /// image is already rendered in the [ItemDescription] widget.
+ final descriptionContainImage =
+ parse(item.description).querySelectorAll('img').isNotEmpty;
+
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.start,
@@ -30,13 +50,11 @@ class ItemDetailsRSS extends StatelessWidget {
item: item,
source: source,
),
- ItemMedia(
- itemMedia: item.media,
- ),
+ _buildImage(!descriptionContainImage),
ItemDescription(
itemDescription: item.description,
- sourceFormat: DescriptionFormat.plain,
- tagetFormat: DescriptionFormat.plain,
+ sourceFormat: DescriptionFormat.html,
+ tagetFormat: DescriptionFormat.markdown,
),
],
);
diff --git a/app/lib/widgets/item/details/utils/item_description.dart b/app/lib/widgets/item/details/utils/item_description.dart
index 461eb9a..8f24caa 100644
--- a/app/lib/widgets/item/details/utils/item_description.dart
+++ b/app/lib/widgets/item/details/utils/item_description.dart
@@ -47,7 +47,7 @@ class ItemDescription extends StatelessWidget {
Widget _buildMarkdown(BuildContext context, String content) {
return MarkdownBody(
selectable: true,
- data: content,
+ data: content.trim(),
styleSheet: MarkdownStyleSheet(
code: TextStyle(
fontFamily: getMonospaceFontFamily(),
@@ -135,7 +135,7 @@ class ItemDescription extends StatelessWidget {
/// [_buildPlain] renders the provided [content] as plain text.
Widget _buildPlain(String content) {
return SelectableText(
- content,
+ content.trim(),
textAlign: TextAlign.left,
style: const TextStyle(
fontWeight: FontWeight.normal,
diff --git a/app/lib/widgets/item/details/utils/item_subtitle.dart b/app/lib/widgets/item/details/utils/item_subtitle.dart
index 517300f..6c33183 100644
--- a/app/lib/widgets/item/details/utils/item_subtitle.dart
+++ b/app/lib/widgets/item/details/utils/item_subtitle.dart
@@ -23,9 +23,10 @@ class ItemSubtitle extends StatelessWidget {
@override
Widget build(BuildContext context) {
final sourceType = source.type.toLocalizedString();
- final sourceTitle = source.title != '' ? ' / ${source.title}' : '';
- final author =
- item.author != null && item.author != '' ? ' / ${item.author}' : '';
+ final sourceTitle = source.title != '' ? ' / ${source.title.trim()}' : '';
+ final author = item.author != null && item.author != ''
+ ? ' / ${item.author!.trim()}'
+ : '';
final publishedAt =
' / ${DateFormat.yMMMMd().add_Hm().format(DateTime.fromMillisecondsSinceEpoch(item.publishedAt * 1000))}';
diff --git a/app/lib/widgets/item/preview/item_preview_rss.dart b/app/lib/widgets/item/preview/item_preview_rss.dart
index 89ceade..3f15a50 100644
--- a/app/lib/widgets/item/preview/item_preview_rss.dart
+++ b/app/lib/widgets/item/preview/item_preview_rss.dart
@@ -41,7 +41,7 @@ class ItemPreviewRSS extends StatelessWidget {
),
ItemDescription(
itemDescription: item.description,
- sourceFormat: DescriptionFormat.plain,
+ sourceFormat: DescriptionFormat.html,
tagetFormat: DescriptionFormat.plain,
),
],
diff --git a/app/lib/widgets/item/preview/utils/item_description.dart b/app/lib/widgets/item/preview/utils/item_description.dart
index 62f17f1..cf5f82b 100644
--- a/app/lib/widgets/item/preview/utils/item_description.dart
+++ b/app/lib/widgets/item/preview/utils/item_description.dart
@@ -46,7 +46,7 @@ class ItemDescription extends StatelessWidget {
),
child: MarkdownBody(
selectable: false,
- data: content,
+ data: content.trim(),
styleSheet: MarkdownStyleSheet(
code: TextStyle(
fontFamily: getMonospaceFontFamily(),
@@ -87,7 +87,7 @@ class ItemDescription extends StatelessWidget {
bottom: Constants.spacingExtraSmall,
),
child: Text(
- content,
+ content.trim(),
maxLines: 5,
style: const TextStyle(
overflow: TextOverflow.ellipsis,
diff --git a/app/pubspec.lock b/app/pubspec.lock
index d5a5822..be39747 100644
--- a/app/pubspec.lock
+++ b/app/pubspec.lock
@@ -321,7 +321,7 @@ packages:
source: hosted
version: "1.1.0"
html:
- dependency: transitive
+ dependency: "direct main"
description:
name: html
sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a"
diff --git a/app/pubspec.yaml b/app/pubspec.yaml
index f231eaa..5e13605 100644
--- a/app/pubspec.yaml
+++ b/app/pubspec.yaml
@@ -43,6 +43,7 @@ dependencies:
collection: ^1.17.0
flutter_markdown: ^0.6.14
flutter_native_splash: ^2.2.19
+ html: ^0.15.4
html2md: ^1.2.6
intl: ^0.18.1
just_audio: ^0.9.32
diff --git a/supabase/functions/_shared/feed/rss.ts b/supabase/functions/_shared/feed/rss.ts
index 07dfd08..04350c6 100644
--- a/supabase/functions/_shared/feed/rss.ts
+++ b/supabase/functions/_shared/feed/rss.ts
@@ -232,7 +232,7 @@ const getMedia = (entry: FeedEntry): string | undefined => {
for (const media of entry["media:content"]) {
if (
media.medium && media.medium === "image" && media.url &&
- media.url.startsWith("https://")
+ media.url.startsWith("https://") && !media.url.endsWith(".svg")
) {
return media.url;
}
@@ -253,7 +253,8 @@ const getMedia = (entry: FeedEntry): string | undefined => {
if (
mediaContent.medium && mediaContent.medium === "image" &&
mediaContent.url &&
- mediaContent.url.startsWith("https://")
+ mediaContent.url.startsWith("https://") &&
+ !mediaContent.url.endsWith(".svg")
) {
return mediaContent.url;
}
@@ -267,7 +268,8 @@ const getMedia = (entry: FeedEntry): string | undefined => {
if (
attachment.mimeType && attachment.mimeType.startsWith("image/") &&
attachment.url &&
- attachment.url.startsWith("https://")
+ attachment.url.startsWith("https://") &&
+ !attachment.url.endsWith(".svg")
) {
return attachment.url;
}
@@ -278,7 +280,10 @@ const getMedia = (entry: FeedEntry): string | undefined => {
const matches = /
]+\bsrc=["']([^"']+)["']/.exec(
entry.description?.value,
);
- if (matches && matches.length == 2 && matches[1].startsWith("https://")) {
+ if (
+ matches && matches.length == 2 && matches[1].startsWith("https://") &&
+ !matches[1].endsWith(".svg")
+ ) {
return matches[1];
}
}
@@ -287,7 +292,10 @@ const getMedia = (entry: FeedEntry): string | undefined => {
const matches = /
]+\bsrc=["']([^"']+)["']/.exec(
entry.content?.value,
);
- if (matches && matches.length == 2 && matches[1].startsWith("https://")) {
+ if (
+ matches && matches.length == 2 && matches[1].startsWith("https://") &&
+ !matches[1].endsWith(".svg")
+ ) {
return matches[1];
}
}