diff --git a/ftl/core/deck-config.ftl b/ftl/core/deck-config.ftl
index 868a8fdb8..eb86c1f23 100644
--- a/ftl/core/deck-config.ftl
+++ b/ftl/core/deck-config.ftl
@@ -6,3 +6,5 @@ deck-config-used-by-decks =
*[other] { $decks } decks
}
deck-config-default-name = Default
+deck-config-description-markdown = Enable markdown+clean HTML
+deck-config-description-markdown-hint = Will appear as text on Anki 2.1.40 and below.
diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py
index 1d7c442e7..d909832a7 100644
--- a/pylib/anki/collection.py
+++ b/pylib/anki/collection.py
@@ -867,6 +867,10 @@ table.review-log {{ {revlog_style} }}
def set_preferences(self, prefs: Preferences) -> None:
self._backend.set_preferences(prefs)
+ def render_markdown(self, text: str, sanitize: bool = True) -> str:
+ "Not intended for public consumption at this time."
+ return self._backend.render_markdown(markdown=text, sanitize=sanitize)
+
# legacy name
_Collection = Collection
diff --git a/qt/aqt/deckconf.py b/qt/aqt/deckconf.py
index 4b9e59c03..1441b68c0 100644
--- a/qt/aqt/deckconf.py
+++ b/qt/aqt/deckconf.py
@@ -236,6 +236,7 @@ class DeckConf(QDialog):
f.autoplaySounds.setChecked(c["autoplay"])
f.replayQuestion.setChecked(c.get("replayq", True))
# description
+ f.enable_markdown.setChecked(self.deck.get("md", False))
f.desc.setPlainText(self.deck["desc"])
gui_hooks.deck_conf_did_load_config(self, self.deck, self.conf)
@@ -320,6 +321,7 @@ class DeckConf(QDialog):
c["autoplay"] = f.autoplaySounds.isChecked()
c["replayq"] = f.replayQuestion.isChecked()
# description
+ self.deck["md"] = f.enable_markdown.isChecked()
self.deck["desc"] = f.desc.toPlainText()
gui_hooks.deck_conf_will_save_config(self, self.deck, self.conf)
self.mw.col.decks.save(self.deck)
diff --git a/qt/aqt/forms/dconf.ui b/qt/aqt/forms/dconf.ui
index b2ce0f719..78480251f 100644
--- a/qt/aqt/forms/dconf.ui
+++ b/qt/aqt/forms/dconf.ui
@@ -6,7 +6,7 @@
"
if deck["dyn"]:
diff --git a/rslib/backend.proto b/rslib/backend.proto
index 245d94adc..cdb8b27cd 100644
--- a/rslib/backend.proto
+++ b/rslib/backend.proto
@@ -207,11 +207,12 @@ service BackendService {
rpc FullDownload(SyncAuth) returns (Empty);
rpc SyncServerMethod(SyncServerMethodIn) returns (Json);
- // translation/messages
+ // translation/messages/text manipulation
rpc TranslateString(TranslateStringIn) returns (String);
rpc FormatTimespan(FormatTimespanIn) returns (String);
rpc I18nResources(Empty) returns (Json);
+ rpc RenderMarkdown(RenderMarkdownIn) returns (String);
// tags
@@ -303,6 +304,8 @@ message DeckCommon {
// but not currently used for anything
int32 learning_studied = 6;
+ reserved 8 to 13;
+
bytes other = 255;
}
@@ -318,6 +321,9 @@ message NormalDeck {
uint32 extend_new = 2;
uint32 extend_review = 3;
string description = 4;
+ bool markdown_description = 5;
+
+ reserved 6 to 11;
}
message FilteredDeck {
@@ -1245,3 +1251,8 @@ message SetConfigStringIn {
Config.String.Key key = 1;
string value = 2;
}
+
+message RenderMarkdownIn {
+ string markdown = 1;
+ bool sanitize = 2;
+}
diff --git a/rslib/src/backend/mod.rs b/rslib/src/backend/mod.rs
index 265675f24..b80b16868 100644
--- a/rslib/src/backend/mod.rs
+++ b/rslib/src/backend/mod.rs
@@ -23,6 +23,7 @@ use crate::{
latex::{extract_latex, extract_latex_expanding_clozes, ExtractedLatex},
log,
log::default_logger,
+ markdown::render_markdown,
media::check::MediaChecker,
media::sync::MediaSyncProgress,
media::MediaManager,
@@ -48,7 +49,7 @@ use crate::{
SyncStage,
},
template::RenderedNode,
- text::{escape_anki_wildcards, extract_av_tags, strip_av_tags, AVTag},
+ text::{escape_anki_wildcards, extract_av_tags, sanitize_html_no_images, strip_av_tags, AVTag},
timestamp::TimestampSecs,
types::Usn,
};
@@ -1402,6 +1403,15 @@ impl BackendService for Backend {
.map_err(Into::into)
}
+ fn render_markdown(&self, input: pb::RenderMarkdownIn) -> BackendResult