summary refs log tree commit diff
diff options
context:
space:
mode:
-rwxr-xr-xbuild.rs2
-rw-r--r--data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in15
-rw-r--r--po/bowl.pot108
-rw-r--r--po/ru.po121
-rw-r--r--src/components/post_editor.rs36
-rw-r--r--src/components/preferences.rs51
-rw-r--r--src/lib.rs10
7 files changed, 237 insertions, 106 deletions
diff --git a/build.rs b/build.rs
index 6eb4244..b536da6 100755
--- a/build.rs
+++ b/build.rs
@@ -30,6 +30,6 @@ fn main() {
         None::<&str>,
         Some("./icons"),
         // Stock icons to include
-        ["menu", "brain-augemnted", "paper-plane" /* sic! */ ],
+        ["menu", "brain-augemnted" /* sic! */, "paper-plane", "editor"],
     );
 }
diff --git a/data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in b/data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in
index 1037063..c7eb986 100644
--- a/data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in
+++ b/data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in
@@ -1,6 +1,21 @@
 <?xml version="1.0" encoding="utf-8"?>
 <schemalist>
   <schema path="/xyz/vikanezrimaya/kittybox/Bowl/" id="@app-id@" gettext-domain="@gettext-package@">
+    <key name="send-html-directly" type="b">
+      <default>false</default>
+      <summary>Send post content as HTML</summary>
+      <description>
+        Some Micropub servers can preprocess plain-text content before
+        posting. Enable this option to ask the Micropub server to
+        treat your post content data as HTML and do not apply usual
+        plain-text processing.
+
+        This could be useful in case you wish to customize the post
+        content using features not available in your Micropub server's
+        preprocessor, or if your Micropub server lacks the ability to
+        preprocess content entirely.
+      </description>
+    </key>
     <key name="llm-endpoint" type="s">
       <default>"http://localhost:11434/"</default>
       <summary>LLM API endpoint</summary>
diff --git a/po/bowl.pot b/po/bowl.pot
index 3fc227e..08d5460 100644
--- a/po/bowl.pot
+++ b/po/bowl.pot
@@ -6,18 +6,18 @@
 #, fuzzy
 msgid ""
 msgstr ""
-"Project-Id-Version: bowl 1.2.0\n"
+"Project-Id-Version: bowl\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-09-04 15:59+0300\n"
+"POT-Creation-Date: 2025-03-30 00:32+0300\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
 "Language: \n"
 "MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: data/xyz.vikanezrimaya.kittybox.Bowl.desktop.in.in:3 src/lib.rs:408
+#: data/xyz.vikanezrimaya.kittybox.Bowl.desktop.in.in:3
 msgid "Bowl"
 msgstr ""
 
@@ -31,37 +31,51 @@ msgid "Micropub;IndieWeb;Kittybox;"
 msgstr ""
 
 #: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:6
-msgid "LLM API endpoint"
+msgid "Send post content as HTML"
 msgstr ""
 
 #: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:7
+msgid ""
+"Some Micropub servers can preprocess plain-text content before posting. "
+"Enable this option to ask the Micropub server to treat your post content "
+"data as HTML and do not apply usual plain-text processing. This could be "
+"useful in case you wish to customize the post content using features not "
+"available in your Micropub server's preprocessor, or if your Micropub server "
+"lacks the ability to preprocess content entirely."
+msgstr ""
+
+#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:21
+msgid "LLM API endpoint"
+msgstr ""
+
+#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:22
 msgid "Ollama API endpoint used to query an LLM for Smart Summary."
 msgstr ""
 
-#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:13
+#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:28
 msgid "Smart Summary LLM"
 msgstr ""
 
 #. TRANSLATORS: please keep the link intact
-#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:15
+#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:30
 msgid ""
 "The model that Ollama will load to produce summaries. Available models can "
 "be seen at <a href=\"https://ollama.com/library\">Ollama library</a>."
 msgstr ""
 
-#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:25
+#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:40
 msgid "Show warnings on LLM enhancement features"
 msgstr ""
 
-#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:26
+#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:41
 msgid "If enabled, will show warnings regarding LLM enhancement features."
 msgstr ""
 
-#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:33
+#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:48
 msgid "LLM system prompt"
 msgstr ""
 
-#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:34
+#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:49
 msgid ""
 "The system prompt provided to the LLM. For best results, it should instruct "
 "the LLM to provide a one-sentence summary of the document it receives. The "
@@ -69,21 +83,21 @@ msgid ""
 "written mainly in English. Performance with other languages is untested."
 msgstr ""
 
-#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:46
+#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:61
 msgid "Smart Summary prompt prefix"
 msgstr ""
 
-#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:47
+#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:62
 msgid ""
 "What the text is prefixed with when pasted into the LLM prompt. Something "
 "like \"Summarize this text:\" works well."
 msgstr ""
 
-#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:55
+#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:70
 msgid "Smart Summary prompt suffix"
 msgstr ""
 
-#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:56
+#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:71
 msgid "Append this to the prompt after the article text."
 msgstr ""
 
@@ -120,39 +134,39 @@ msgstr ""
 msgid "Proceed"
 msgstr ""
 
-#: src/components/post_editor.rs:147
+#: src/components/post_editor.rs:159
 msgid "Name"
 msgstr ""
 
-#: src/components/post_editor.rs:162
+#: src/components/post_editor.rs:174
 msgid "Summary"
 msgstr ""
 
-#: src/components/post_editor.rs:182
+#: src/components/post_editor.rs:194
 msgid "Tags"
 msgstr ""
 
-#: src/components/post_editor.rs:218
+#: src/components/post_editor.rs:230
 msgid "Content"
 msgstr ""
 
-#: src/components/post_editor.rs:271
+#: src/components/post_editor.rs:283
 msgid "Visibility"
 msgstr ""
 
-#: src/components/post_editor.rs:450
+#: src/components/post_editor.rs:462
 msgid "Smart Summary error: {}"
 msgstr ""
 
-#: src/components/post_editor.rs:497
+#: src/components/post_editor.rs:509
 msgid "Post submitted"
 msgstr ""
 
-#: src/components/post_editor.rs:498
+#: src/components/post_editor.rs:510
 msgid "Open"
 msgstr ""
 
-#: src/components/post_editor.rs:516
+#: src/components/post_editor.rs:528
 msgid "Error sending post: {}"
 msgstr ""
 
@@ -160,72 +174,80 @@ msgstr ""
 msgid "Thank you! This window can now be closed."
 msgstr ""
 
-#: src/components/signin.rs:270 src/components/signin.rs:309
+#: src/components/signin.rs:272 src/components/signin.rs:311
 msgid "Sign in"
 msgstr ""
 
-#: src/components/signin.rs:275
+#: src/components/signin.rs:277
 msgid ""
 "Please sign in with your website to use Bowl.\n"
 "Your website needs to support IndieAuth and Micropub for this app to work."
 msgstr ""
 
-#: src/components/signin.rs:305
+#: src/components/signin.rs:307
 msgid "Talking to your website..."
 msgstr ""
 
-#: src/components/signin.rs:307
+#: src/components/signin.rs:309
 msgid "Waiting for authorization..."
 msgstr ""
 
-#: src/components/signin.rs:431
+#: src/components/signin.rs:433
 msgid "state doesn't match what we remember, ceremony aborted"
 msgstr ""
 
-#: src/components/signin.rs:439
+#: src/components/signin.rs:441
 msgid "issuer doesn't match what we remember, ceremony aborted"
 msgstr ""
 
-#: src/components/preferences.rs:31
-msgid "Language Models"
+#: src/components/preferences.rs:21
+msgid "Post composer"
 msgstr ""
 
-#: src/components/preferences.rs:32
-msgid "Settings for the language model integrations."
+#: src/components/preferences.rs:22
+msgid "Settings for composing new posts and editing existing ones."
 msgstr ""
 
-#: src/components/preferences.rs:37
+#: src/components/preferences.rs:26 src/components/preferences.rs:83
 msgid "General"
 msgstr ""
 
-#: src/components/preferences.rs:46
+#: src/components/preferences.rs:77
+msgid "Language models"
+msgstr ""
+
+#: src/components/preferences.rs:78
+msgid "Settings for the language model integrations."
+msgstr ""
+
+#: src/components/preferences.rs:92
 msgid "Smart Summary"
 msgstr ""
 
-#: src/lib.rs:77
+#: src/lib.rs:78
 msgid "Micropub access token for {}"
 msgstr ""
 
-#: src/lib.rs:352
+#: src/lib.rs:353
 msgid "Bowl for Kittybox"
 msgstr ""
 
-#: src/lib.rs:390
+#: src/lib.rs:391
 msgid "Sign out"
 msgstr ""
 
-#: src/lib.rs:391
+#: src/lib.rs:392
 msgid "Preferences"
 msgstr ""
 
-#: src/lib.rs:392
+#: src/lib.rs:393
 msgid "About"
 msgstr ""
 
-#: src/lib.rs:406
+#: src/lib.rs:407
 msgid "Bowl - Sign in with your website"
 msgstr ""
 
-#: src/lib.rs:422
+#: src/lib.rs:423
 msgid "Publish"
 msgstr ""
diff --git a/po/ru.po b/po/ru.po
index 308f541..e6cd2cb 100644
--- a/po/ru.po
+++ b/po/ru.po
@@ -6,8 +6,8 @@
 msgid ""
 msgstr ""
 "Project-Id-Version: bowl 1.2.0\n"
-"Report-Msgid-Bugs-To: vika@fireburn.ru\n"
-"POT-Creation-Date: 2024-09-04 15:59+0300\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2025-03-30 00:32+0300\n"
 "PO-Revision-Date: 2025-02-24 04:33+0300\n"
 "Last-Translator: Vika <vika@fireburn.ru>\n"
 "Language-Team: Russian <gnu@d07.ru>\n"
@@ -18,7 +18,7 @@ msgstr ""
 "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
 "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
 
-#: data/xyz.vikanezrimaya.kittybox.Bowl.desktop.in.in:3 src/lib.rs:408
+#: data/xyz.vikanezrimaya.kittybox.Bowl.desktop.in.in:3 src/lib.rs:409
 msgid "Bowl"
 msgstr "Bowl"
 
@@ -32,21 +32,41 @@ msgid "Micropub;IndieWeb;Kittybox;"
 msgstr "Micropub;IndieWeb;Kittybox;"
 
 #: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:6
+msgid "Send post content as HTML"
+msgstr "Отправлять содержание поста в формате HTML"
+
+#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:7
+msgid ""
+"Some Micropub servers can preprocess plain-text content before posting. "
+"Enable this option to ask the Micropub server to treat your post content "
+"data as HTML and do not apply usual plain-text processing. This could be "
+"useful in case you wish to customize the post content using features not "
+"available in your Micropub server's preprocessor, or if your Micropub server "
+"lacks the ability to preprocess content entirely."
+msgstr ""
+"Некоторые сервера Micropub могут обрабатывать текстовое содержание поста "
+"перед публикацией. Включите эту опцию, чтобы Ваш Micropub-сервер считал "
+"содержание поста уже в формате HTML и не применял к нему обычный процессинг. "
+"Это может быть полезно в случае, если Вы хотите добавлять в содержание поста "
+"разметку, не поддерживаемую препроцессором Вашего Micropub-сервера, либо "
+"если Ваш Micropub-сервер неспособен конвертировать текстовые посты в HTML."
+
+#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:21
 msgid "LLM API endpoint"
 msgstr "Точка API LLM"
 
-#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:7
+#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:22
 msgid "Ollama API endpoint used to query an LLM for Smart Summary."
 msgstr ""
 "API Ollama, которое используется, чтобы сгенерировать Умную Выжимку с "
 "помощью языковой модели."
 
-#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:13
+#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:28
 msgid "Smart Summary LLM"
 msgstr "Модель для Умной Выжимки"
 
 #. TRANSLATORS: please keep the link intact
-#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:15
+#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:30
 msgid ""
 "The model that Ollama will load to produce summaries. Available models can "
 "be seen at <a href=\"https://ollama.com/library\">Ollama library</a>."
@@ -55,20 +75,21 @@ msgstr ""
 "Доступные модели можно увидеть в <a href=\"https://ollama.com/"
 "library\">библиотеке Ollama</a>."
 
-#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:25
+#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:40
 msgid "Show warnings on LLM enhancement features"
 msgstr "Показывать предупреждения о языковых моделях"
 
-#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:26
+#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:41
 msgid "If enabled, will show warnings regarding LLM enhancement features."
-msgstr "При включении приложение будет показывать предупреждения при "
-"использовании функций, задействующих языковые модели."
+msgstr ""
+"При включении приложение будет показывать предупреждения при использовании "
+"функций, задействующих языковые модели."
 
-#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:33
+#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:48
 msgid "LLM system prompt"
 msgstr "Системная вводная для LLM"
 
-#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:34
+#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:49
 msgid ""
 "The system prompt provided to the LLM. For best results, it should instruct "
 "the LLM to provide a one-sentence summary of the document it receives. The "
@@ -81,11 +102,11 @@ msgstr ""
 "протестирована для Llama 3.1-8B и лучше всего работает со статьями на "
 "английском. Результаты для других языков не гарантированы."
 
-#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:46
+#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:61
 msgid "Smart Summary prompt prefix"
 msgstr "Префикс вводной для Умной Выжимки"
 
-#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:47
+#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:62
 msgid ""
 "What the text is prefixed with when pasted into the LLM prompt. Something "
 "like \"Summarize this text:\" works well."
@@ -93,11 +114,11 @@ msgstr ""
 "Что приписывается к началу текста для вводной языковой модели. Пример: "
 "\"Опиши смысл этого текста одним предложением:\""
 
-#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:55
+#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:70
 msgid "Smart Summary prompt suffix"
 msgstr "Суффикс вводной Умной Выжимки"
 
-#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:56
+#: data/xyz.vikanezrimaya.kittybox.Bowl.gschema.xml.in:71
 msgid "Append this to the prompt after the article text."
 msgstr "Что приписывается к вводной после текста статьи."
 
@@ -142,39 +163,39 @@ msgstr "Отмена"
 msgid "Proceed"
 msgstr "OK"
 
-#: src/components/post_editor.rs:147
+#: src/components/post_editor.rs:159
 msgid "Name"
 msgstr "Название"
 
-#: src/components/post_editor.rs:162
+#: src/components/post_editor.rs:174
 msgid "Summary"
 msgstr "Содержание"
 
-#: src/components/post_editor.rs:182
+#: src/components/post_editor.rs:194
 msgid "Tags"
 msgstr "Тэги"
 
-#: src/components/post_editor.rs:218
+#: src/components/post_editor.rs:230
 msgid "Content"
 msgstr "Текст"
 
-#: src/components/post_editor.rs:271
+#: src/components/post_editor.rs:283
 msgid "Visibility"
 msgstr "Видимость"
 
-#: src/components/post_editor.rs:450
+#: src/components/post_editor.rs:462
 msgid "Smart Summary error: {}"
 msgstr "Ошибка Умной Выжимки: {}"
 
-#: src/components/post_editor.rs:497
+#: src/components/post_editor.rs:509
 msgid "Post submitted"
 msgstr "Статья отправлена"
 
-#: src/components/post_editor.rs:498
+#: src/components/post_editor.rs:510
 msgid "Open"
 msgstr "Открыть"
 
-#: src/components/post_editor.rs:516
+#: src/components/post_editor.rs:528
 msgid "Error sending post: {}"
 msgstr "Ошибка отправки статьи: {}"
 
@@ -182,11 +203,11 @@ msgstr "Ошибка отправки статьи: {}"
 msgid "Thank you! This window can now be closed."
 msgstr "Благодарим Вас! Это окно можно закрыть."
 
-#: src/components/signin.rs:270 src/components/signin.rs:309
+#: src/components/signin.rs:272 src/components/signin.rs:311
 msgid "Sign in"
 msgstr "Войти"
 
-#: src/components/signin.rs:275
+#: src/components/signin.rs:277
 msgid ""
 "Please sign in with your website to use Bowl.\n"
 "Your website needs to support IndieAuth and Micropub for this app to work."
@@ -195,62 +216,70 @@ msgstr ""
 "Ваш веб-сайт должен поддерживать протоколы IndieAuth и Micropub для "
 "корректной работы приложения."
 
-#: src/components/signin.rs:305
+#: src/components/signin.rs:307
 msgid "Talking to your website..."
 msgstr "Общаемся с Вашим веб-сайтом..."
 
-#: src/components/signin.rs:307
+#: src/components/signin.rs:309
 msgid "Waiting for authorization..."
 msgstr "Ждём авторизации..."
 
-#: src/components/signin.rs:431
+#: src/components/signin.rs:433
 msgid "state doesn't match what we remember, ceremony aborted"
 msgstr "поле state не совпадает с тем, что мы помним, церемония отменена"
 
-#: src/components/signin.rs:439
+#: src/components/signin.rs:441
 msgid "issuer doesn't match what we remember, ceremony aborted"
 msgstr "поле issuer не совпадает с тем, что мы помним, церемония отменена"
 
-#: src/components/preferences.rs:31
-msgid "Language Models"
-msgstr "Языковые модели"
+#: src/components/preferences.rs:21
+msgid "Post composer"
+msgstr "Редактор постов"
 
-#: src/components/preferences.rs:32
-msgid "Settings for the language model integrations."
-msgstr "Настройки интеграции языковых моделей."
+#: src/components/preferences.rs:22
+msgid "Settings for composing new posts and editing existing ones."
+msgstr "Настройки создания и редактирования постов."
 
-#: src/components/preferences.rs:37
+#: src/components/preferences.rs:26 src/components/preferences.rs:83
 msgid "General"
 msgstr "Общее"
 
-#: src/components/preferences.rs:46
+#: src/components/preferences.rs:77
+msgid "Language models"
+msgstr "Языковые модели"
+
+#: src/components/preferences.rs:78
+msgid "Settings for the language model integrations."
+msgstr "Настройки интеграции языковых моделей."
+
+#: src/components/preferences.rs:92
 msgid "Smart Summary"
 msgstr "Умная Выжимка"
 
-#: src/lib.rs:77
+#: src/lib.rs:78
 msgid "Micropub access token for {}"
 msgstr "Токен доступа Micropub для {}"
 
-#: src/lib.rs:352
+#: src/lib.rs:353
 msgid "Bowl for Kittybox"
 msgstr "Bowl для Kittybox"
 
-#: src/lib.rs:390
+#: src/lib.rs:391
 msgid "Sign out"
 msgstr "Выйти"
 
-#: src/lib.rs:391
+#: src/lib.rs:392
 msgid "Preferences"
 msgstr "Настройки"
 
-#: src/lib.rs:392
+#: src/lib.rs:393
 msgid "About"
 msgstr "О приложении"
 
-#: src/lib.rs:406
+#: src/lib.rs:407
 msgid "Bowl - Sign in with your website"
 msgstr "Bowl - Войдите со своим веб-сайтом"
 
-#: src/lib.rs:422
+#: src/lib.rs:423
 msgid "Publish"
 msgstr "Опубликовать"
diff --git a/src/components/post_editor.rs b/src/components/post_editor.rs
index 25069be..863e515 100644
--- a/src/components/post_editor.rs
+++ b/src/components/post_editor.rs
@@ -32,40 +32,52 @@ pub struct Post {
     pub visibility: Visibility
 }
 
-impl From<Post> for microformats::types::Item {
-    fn from(post: Post) -> Self {
-        use microformats::types::{Item, Class, KnownClass, PropertyValue};
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
+pub struct PostConversionSettings {
+    pub send_html_directly: bool,
+}
+
+impl Post {
+    pub fn into_mf2(self, settings: PostConversionSettings) -> microformats::types::Item {
+        use microformats::types::{Item, Class, KnownClass, PropertyValue, Fragment};
         let mut mf2 = Item::new(vec![Class::Known(KnownClass::Entry)]);
 
-        if let Some(name) = post.name {
+        if let Some(name) = self.name {
             mf2.properties.insert(
                 "name".to_owned(), vec![PropertyValue::Plain(name)]
             );
         }
 
-        if let Some(summary) = post.summary {
+        if let Some(summary) = self.summary {
             mf2.properties.insert(
                 "summary".to_owned(),
                 vec![PropertyValue::Plain(summary)]
             );
         }
 
-        if !post.tags.is_empty() {
+        if !self.tags.is_empty() {
             mf2.properties.insert(
                 "category".to_string(),
-                post.tags.into_iter().map(PropertyValue::Plain).collect()
+                self.tags.into_iter().map(PropertyValue::Plain).collect()
             );
         }
 
         mf2.properties.insert(
             "visibility".to_string(),
-            vec![PropertyValue::Plain(post.visibility.to_string())]
+            vec![PropertyValue::Plain(self.visibility.to_string())]
         );
 
-        mf2.properties.insert(
-            "content".to_string(),
-            vec![PropertyValue::Plain(post.content)]
-        );
+        let content = if settings.send_html_directly {
+            PropertyValue::Fragment(Fragment {
+                html: self.content.clone(),
+                value: self.content,
+                lang: None
+            })
+        } else {
+            PropertyValue::Plain(self.content)
+        };
+
+        mf2.properties.insert("content".to_string(), vec![content]);
 
         mf2
     }
diff --git a/src/components/preferences.rs b/src/components/preferences.rs
index 4c2c9fd..67075a2 100644
--- a/src/components/preferences.rs
+++ b/src/components/preferences.rs
@@ -8,6 +8,52 @@ pub struct Preferences {
     settings: gio::Settings,
 }
 
+#[allow(dead_code)]
+struct ComposerPreferencesWidgets {
+    page: adw::PreferencesPage,
+    general_group: adw::PreferencesGroup,
+    send_html_directly: adw::SwitchRow,
+}
+
+impl ComposerPreferencesWidgets {
+    fn new(settings: &gio::Settings) -> Self {
+        let page = adw::PreferencesPage::builder()
+            .title(gettext("Post composer"))
+            .description(gettext("Settings for composing new posts and editing existing ones."))
+            .icon_name("editor-symbolic")
+            .build();
+        let general_group = adw::PreferencesGroup::builder()
+            .title(gettext("General"))
+            .build();
+        let send_html_directly = adw::SwitchRow::new();
+        general_group.add(&send_html_directly);
+        page.add(&general_group);
+
+        let widgets = Self {
+            page,
+            general_group,
+            send_html_directly
+        };
+
+        let schema = settings.settings_schema().unwrap();
+
+        #[expect(clippy::single_element_loop)]
+        for (row, key, property) in [
+            (widgets.send_html_directly.upcast_ref::<adw::PreferencesRow>(), "send-html-directly", "active"),
+        ] {
+            let key_data = schema.key(key);
+            settings.bind(key, row, property)
+                .get()
+                .set()
+                .build();
+            row.set_title(&gettext(key_data.summary().unwrap()));
+            row.set_tooltip_markup(key_data.description().map(gettext).as_deref());
+        }
+
+        widgets
+    }
+}
+
 #[cfg(feature = "smart-summary")]
 #[allow(dead_code)]
 struct LanguageModelPreferencesWidgets {
@@ -28,7 +74,7 @@ struct LanguageModelPreferencesWidgets {
 impl LanguageModelPreferencesWidgets {
     fn new(settings: &gio::Settings) -> Self {
         let page = adw::PreferencesPage::builder()
-            .title(gettext("Language Models"))
+            .title(gettext("Language models"))
             .description(gettext("Settings for the language model integrations."))
             .icon_name("brain-augemnted") // sic!
             .build();
@@ -93,6 +139,7 @@ impl LanguageModelPreferencesWidgets {
 }
 
 pub struct PreferencesWidgets {
+    composer: ComposerPreferencesWidgets,
     #[cfg(feature = "smart-summary")]
     llm: LanguageModelPreferencesWidgets,
 }
@@ -121,9 +168,11 @@ impl Component for Preferences {
         model.settings.delay();
 
         let widgets = PreferencesWidgets {
+            composer: ComposerPreferencesWidgets::new(&model.settings),
             #[cfg(feature = "smart-summary")]
             llm: LanguageModelPreferencesWidgets::new(&model.settings),
         };
+        root.add(&widgets.composer.page);
         #[cfg(feature = "smart-summary")]
         root.add(&widgets.llm.page);
 
diff --git a/src/lib.rs b/src/lib.rs
index 0520e0a..fd4b51c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -29,7 +29,7 @@ pub mod components {
     pub use preferences::Preferences;
 }
 
-use components::{post_editor::Post, PostEditorInput};
+use components::{post_editor::{Post, PostConversionSettings}, PostEditorInput};
 use soup::prelude::SessionExt;
 
 pub mod secrets;
@@ -43,6 +43,7 @@ pub struct App {
     secret_schema: libsecret::Schema,
     http: soup::Session,
     submit_busy_guard: Option<gtk::gio::ApplicationBusyGuard>,
+    settings: gio::Settings,
 
     // TODO: make this support multiple users
     micropub: Option<micropub::Client>,
@@ -415,7 +416,7 @@ impl AsyncComponent for App {
                         set_popover = &gtk::PopoverMenu::from_model(Some(&main_menu)) {},
                         #[watch]
                         set_visible: model.micropub.is_some(),
-                        set_icon_name: "menu",
+                        set_icon_name: "menu-symbolic",
                     },
                     pack_end = &gtk::Button {
                         set_icon_name: "paper-plane",
@@ -473,6 +474,7 @@ impl AsyncComponent for App {
             http: http.clone(),
             micropub: state,
             secret_schema,
+            settings: gio::Settings::new(crate::APPLICATION_ID),
 
             post_editor: {
                 #[cfg(feature = "smart-summary")]
@@ -561,7 +563,9 @@ impl AsyncComponent for App {
             }
             Input::PostEditor(Some(post)) => {
                 if let Some(micropub) = self.micropub.as_ref() {
-                    let mf2 = post.into();
+                    let mf2 = post.into_mf2(PostConversionSettings {
+                        send_html_directly: self.settings.get("send-html-directly")
+                    });
                     log::debug!("Submitting post: {:#}", serde_json::to_string(&mf2).unwrap());
                     match micropub.send_post(mf2).await {
                         Ok(uri) => {