Markdown++

This commit is contained in:
2025-10-12 18:22:16 +08:00
parent f9d033542e
commit 41c56a2319
2 changed files with 158 additions and 3 deletions

View File

@@ -1219,5 +1219,6 @@
"noStickers": "No Stickers", "noStickers": "No Stickers",
"noStickersInPack": "This pack does not contains stickers", "noStickersInPack": "This pack does not contains stickers",
"noStickerPacks": "No Sticker Packs", "noStickerPacks": "No Sticker Packs",
"refresh": "Refresh" "refresh": "Refresh",
"spoiler": "Spoiler"
} }

View File

@@ -74,6 +74,20 @@ class MarkdownTextContent extends HookConsumerWidget {
onTap: onMentionTap, onTap: onMentionTap,
); );
final highlightGenerator = HighlightGenerator(
highlightColor: Theme.of(context).colorScheme.primaryContainer,
);
final spoilerRevealed = useState(false);
final spoilerGenerator = SpoilerGenerator(
backgroundColor: Theme.of(context).colorScheme.tertiary,
foregroundColor: Theme.of(context).colorScheme.onTertiary,
outlineColor: Theme.of(context).colorScheme.outline,
revealed: spoilerRevealed.value,
onToggle: () => spoilerRevealed.value = !spoilerRevealed.value,
);
return MarkdownBlock( return MarkdownBlock(
data: content, data: content,
selectable: isSelectable, selectable: isSelectable,
@@ -214,7 +228,7 @@ class MarkdownTextContent extends HookConsumerWidget {
generator: MarkdownTextContent.buildGenerator( generator: MarkdownTextContent.buildGenerator(
isDark: isDark, isDark: isDark,
linesMargin: linesMargin, linesMargin: linesMargin,
generators: [mentionGenerator], generators: [mentionGenerator, highlightGenerator, spoilerGenerator],
), ),
); );
} }
@@ -222,12 +236,14 @@ class MarkdownTextContent extends HookConsumerWidget {
static MarkdownGenerator buildGenerator({ static MarkdownGenerator buildGenerator({
bool isDark = false, bool isDark = false,
EdgeInsets? linesMargin, EdgeInsets? linesMargin,
List<SpanNodeGeneratorWithTag> generators = const [], List<dynamic> generators = const [],
}) { }) {
return MarkdownGenerator( return MarkdownGenerator(
generators: [latexGenerator, ...generators], generators: [latexGenerator, ...generators],
inlineSyntaxList: [ inlineSyntaxList: [
_MetionInlineSyntax(), _MetionInlineSyntax(),
_HighlightInlineSyntax(),
_SpoilerInlineSyntax(),
_StickerInlineSyntax(), _StickerInlineSyntax(),
LatexSyntax(isDark), LatexSyntax(isDark),
], ],
@@ -276,6 +292,32 @@ class _StickerInlineSyntax extends markdown.InlineSyntax {
} }
} }
class _HighlightInlineSyntax extends markdown.InlineSyntax {
_HighlightInlineSyntax() : super(r'==([^=]+)==');
@override
bool onMatch(markdown.InlineParser parser, Match match) {
final text = match[1]!;
final element = markdown.Element('highlight', [markdown.Text(text)]);
parser.addNode(element);
return true;
}
}
class _SpoilerInlineSyntax extends markdown.InlineSyntax {
_SpoilerInlineSyntax() : super(r'=!([^!]+)!=');
@override
bool onMatch(markdown.InlineParser parser, Match match) {
final text = match[1]!;
final element = markdown.Element('spoiler', [markdown.Text(text)]);
parser.addNode(element);
return true;
}
}
class MentionSpanNodeGenerator { class MentionSpanNodeGenerator {
final Color backgroundColor; final Color backgroundColor;
final Color foregroundColor; final Color foregroundColor;
@@ -394,3 +436,115 @@ class MentionChipSpanNode extends SpanNode {
); );
} }
} }
class HighlightGenerator extends SpanNodeGeneratorWithTag {
HighlightGenerator({required Color highlightColor})
: super(
tag: 'highlight',
generator: (
markdown.Element element,
MarkdownConfig config,
WidgetVisitor visitor,
) {
return HighlightSpanNode(
text: element.textContent,
highlightColor: highlightColor,
);
},
);
}
class HighlightSpanNode extends SpanNode {
final String text;
final Color highlightColor;
HighlightSpanNode({required this.text, required this.highlightColor});
@override
InlineSpan build() {
return TextSpan(
text: text,
style: TextStyle(backgroundColor: highlightColor),
);
}
}
class SpoilerGenerator extends SpanNodeGeneratorWithTag {
SpoilerGenerator({
required Color backgroundColor,
required Color foregroundColor,
required Color outlineColor,
required bool revealed,
required VoidCallback onToggle,
}) : super(
tag: 'spoiler',
generator: (
markdown.Element element,
MarkdownConfig config,
WidgetVisitor visitor,
) {
return SpoilerSpanNode(
text: element.textContent,
backgroundColor: backgroundColor,
foregroundColor: foregroundColor,
outlineColor: outlineColor,
revealed: revealed,
onToggle: onToggle,
);
},
);
}
class SpoilerSpanNode extends SpanNode {
final String text;
final Color backgroundColor;
final Color foregroundColor;
final Color outlineColor;
final bool revealed;
final VoidCallback onToggle;
SpoilerSpanNode({
required this.text,
required this.backgroundColor,
required this.foregroundColor,
required this.outlineColor,
required this.revealed,
required this.onToggle,
});
@override
InlineSpan build() {
return WidgetSpan(
child: InkWell(
onTap: onToggle,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
decoration: BoxDecoration(
color: revealed ? Colors.transparent : backgroundColor,
border: revealed ? Border.all(color: outlineColor, width: 1) : null,
borderRadius: BorderRadius.circular(4),
),
child:
revealed
? Row(
spacing: 6,
mainAxisSize: MainAxisSize.min,
children: [Icon(Symbols.visibility, size: 18), Text(text)],
)
: Row(
spacing: 6,
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Symbols.visibility_off,
color: foregroundColor,
size: 18,
),
Text(text, style: TextStyle(color: foregroundColor)),
],
),
),
),
);
}
}