Revised revisions

pull/168/head
M66B 5 years ago
parent 482c7eb92c
commit daadc58e97

File diff suppressed because it is too large Load Diff

@ -48,6 +48,8 @@ import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import com.sun.mail.imap.IMAPFolder; import com.sun.mail.imap.IMAPFolder;
import org.jsoup.nodes.Document;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.InputStream; import java.io.InputStream;
@ -135,8 +137,10 @@ public class ActivityEML extends ActivityBase {
result.parts = helper.getMessageParts(); result.parts = helper.getMessageParts();
String html = result.parts.getHtml(context); String html = result.parts.getHtml(context);
if (html != null) if (html != null) {
result.body = HtmlHelper.fromHtml(HtmlHelper.sanitize(context, html, false, false)); Document document = HtmlHelper.sanitize(context, html, false, false);
result.body = HtmlHelper.fromHtml(document.html());
}
return result; return result;
} }

@ -1455,28 +1455,27 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
return document.html(); return document.html();
} else { } else {
// Cleanup message // Cleanup message
String html = HtmlHelper.sanitize(context, body, show_images, true); document = HtmlHelper.sanitize(context, body, show_images, true);
// Collapse quotes // Collapse quotes
if (!show_quotes) { if (!show_quotes) {
Document doc = JsoupEx.parse(html); for (Element quote : document.select("blockquote"))
for (Element quote : doc.select("blockquote"))
quote.html("&#8230;"); quote.html("&#8230;");
html = doc.html();
} }
// Add debug info // Add debug info
if (debug) { if (debug) {
Document format = JsoupEx.parse(html); document.outputSettings().prettyPrint(true).outline(true).indentAmount(1);
format.outputSettings().prettyPrint(true).outline(true).indentAmount(1); String[] lines = document.html().split("\\r?\\n");
String[] lines = format.html().split("\\r?\\n");
for (int i = 0; i < lines.length; i++) for (int i = 0; i < lines.length; i++)
lines[i] = Html.escapeHtml(lines[i]); lines[i] = Html.escapeHtml(lines[i]);
html += "<pre>" + TextUtils.join("<br>", lines) + "</pre>"; Element pre = document.createElement("pre");
pre.html(TextUtils.join("<br>", lines));
document.appendChild(pre);
} }
// Draw images // Draw images
Spanned spanned = HtmlHelper.fromHtml(html, new Html.ImageGetter() { Spanned spanned = HtmlHelper.fromHtml(document.html(), new Html.ImageGetter() {
@Override @Override
public Drawable getDrawable(String source) { public Drawable getDrawable(String source) {
Drawable drawable = ImageHelper.decodeImage(context, message.id, source, show_images, zoom, tvBody); Drawable drawable = ImageHelper.decodeImage(context, message.id, source, show_images, zoom, tvBody);

@ -56,13 +56,12 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory;
// https://developer.android.com/topic/libraries/architecture/room.html // https://developer.android.com/topic/libraries/architecture/room.html
@Database( @Database(
version = 114, version = 115,
entities = { entities = {
EntityIdentity.class, EntityIdentity.class,
EntityAccount.class, EntityAccount.class,
EntityFolder.class, EntityFolder.class,
EntityMessage.class, EntityMessage.class,
EntityRevision.class,
EntityAttachment.class, EntityAttachment.class,
EntityOperation.class, EntityOperation.class,
EntityContact.class, EntityContact.class,
@ -84,8 +83,6 @@ public abstract class DB extends RoomDatabase {
public abstract DaoMessage message(); public abstract DaoMessage message();
public abstract DaoRevision revision();
public abstract DaoAttachment attachment(); public abstract DaoAttachment attachment();
public abstract DaoOperation operation(); public abstract DaoOperation operation();
@ -1113,6 +1110,13 @@ public abstract class DB extends RoomDatabase {
" WHERE encryption = " + EntityAttachment.PGP_MESSAGE + ")"); " WHERE encryption = " + EntityAttachment.PGP_MESSAGE + ")");
} }
}) })
.addMigrations(new Migration(114, 115) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase db) {
Log.i("DB migration from version " + startVersion + " to " + endVersion);
db.execSQL("DROP TABLE revision");
}
})
.build(); .build();
} }

@ -1,35 +0,0 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2019 by Marcel Bokhorst (M66B)
*/
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
@Dao
public interface DaoRevision {
@Query("SELECT * FROM revision" +
" WHERE message = :message" +
" AND sequence = :sequence")
EntityRevision getRevision(long message, int sequence);
@Insert
long insertRevision(EntityRevision revision);
}

@ -37,6 +37,8 @@ import androidx.core.view.inputmethod.EditorInfoCompat;
import androidx.core.view.inputmethod.InputConnectionCompat; import androidx.core.view.inputmethod.InputConnectionCompat;
import androidx.core.view.inputmethod.InputContentInfoCompat; import androidx.core.view.inputmethod.InputContentInfoCompat;
import org.jsoup.nodes.Document;
public class EditTextCompose extends AppCompatEditText { public class EditTextCompose extends AppCompatEditText {
private ISelection selectionListener = null; private ISelection selectionListener = null;
private IInputContentListener inputContentListener = null; private IInputContentListener inputContentListener = null;
@ -70,8 +72,8 @@ public class EditTextCompose extends AppCompatEditText {
ClipData.Item item = clipboard.getPrimaryClip().getItemAt(0); ClipData.Item item = clipboard.getPrimaryClip().getItemAt(0);
String html = item.coerceToHtmlText(context); String html = item.coerceToHtmlText(context);
html = HtmlHelper.sanitize(context, html, false, false); Document document = HtmlHelper.sanitize(context, html, false, false);
Spanned paste = HtmlHelper.fromHtml(html); Spanned paste = HtmlHelper.fromHtml(document.html());
int colorPrimary = Helper.resolveColor(context, R.attr.colorPrimary); int colorPrimary = Helper.resolveColor(context, R.attr.colorPrimary);

@ -228,17 +228,13 @@ public class EntityMessage implements Serializable {
return new File(dir, id + "." + revision); return new File(dir, id + "." + revision);
} }
static File getRefFile(Context context, Long id) { File getRefFile(Context context) {
File dir = new File(context.getFilesDir(), "references"); File dir = new File(context.getFilesDir(), "references");
if (!dir.exists()) if (!dir.exists())
dir.mkdir(); dir.mkdir();
return new File(dir, id.toString()); return new File(dir, id.toString());
} }
File getRefFile(Context context) {
return getRefFile(context, id);
}
File getRawFile(Context context) { File getRawFile(Context context) {
File dir = new File(context.getFilesDir(), "raw"); File dir = new File(context.getFilesDir(), "raw");
if (!dir.exists()) if (!dir.exists())

@ -1,63 +0,0 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2019 by Marcel Bokhorst (M66B)
*/
import androidx.annotation.NonNull;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import static androidx.room.ForeignKey.CASCADE;
@Entity(
tableName = EntityRevision.TABLE_NAME,
foreignKeys = {
@ForeignKey(childColumns = "message", entity = EntityMessage.class, parentColumns = "id", onDelete = CASCADE),
},
indices = {
@Index(value = {"message"}),
@Index(value = {"message", "sequence"}, unique = true)
}
)
public class EntityRevision {
static final String TABLE_NAME = "revision";
@PrimaryKey(autoGenerate = true)
public Long id;
@NonNull
public Long message;
@NonNull
public Integer sequence;
@NonNull
public Boolean reference;
@Override
public boolean equals(Object obj) {
if (obj instanceof EntityRevision) {
EntityRevision other = (EntityRevision) obj;
return (this.message.equals(other.message) &&
this.sequence.equals(other.sequence) &&
this.reference.equals(other.reference));
} else
return false;
}
}

@ -103,10 +103,8 @@ import com.google.android.material.snackbar.Snackbar;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element; import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode; import org.jsoup.nodes.TextNode;
import org.jsoup.select.NodeTraversor; import org.jsoup.select.Elements;
import org.jsoup.select.NodeVisitor;
import org.openintents.openpgp.OpenPgpError; import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpApi;
import org.openintents.openpgp.util.OpenPgpServiceConnection; import org.openintents.openpgp.util.OpenPgpServiceConnection;
@ -168,7 +166,6 @@ public class FragmentCompose extends FragmentBase {
private CheckBox cbSignature; private CheckBox cbSignature;
private TextView tvReference; private TextView tvReference;
private ImageButton ibCloseRefHint; private ImageButton ibCloseRefHint;
private ImageButton ibReferenceDelete;
private ImageButton ibReferenceEdit; private ImageButton ibReferenceEdit;
private ImageButton ibReferenceImages; private ImageButton ibReferenceImages;
private BottomNavigationView style_bar; private BottomNavigationView style_bar;
@ -220,12 +217,11 @@ public class FragmentCompose extends FragmentBase {
private static final int REQUEST_RECORD_AUDIO = 7; private static final int REQUEST_RECORD_AUDIO = 7;
private static final int REQUEST_ENCRYPT = 8; private static final int REQUEST_ENCRYPT = 8;
private static final int REQUEST_COLOR = 9; private static final int REQUEST_COLOR = 9;
private static final int REQUEST_REF_DELETE = 10; private static final int REQUEST_CONTACT_GROUP = 10;
private static final int REQUEST_CONTACT_GROUP = 11; private static final int REQUEST_ANSWER = 11;
private static final int REQUEST_ANSWER = 12; private static final int REQUEST_LINK = 12;
private static final int REQUEST_LINK = 13; private static final int REQUEST_DISCARD = 13;
private static final int REQUEST_DISCARD = 14; private static final int REQUEST_SEND = 14;
private static final int REQUEST_SEND = 15;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@ -268,7 +264,6 @@ public class FragmentCompose extends FragmentBase {
cbSignature = view.findViewById(R.id.cbSignature); cbSignature = view.findViewById(R.id.cbSignature);
tvReference = view.findViewById(R.id.tvReference); tvReference = view.findViewById(R.id.tvReference);
ibCloseRefHint = view.findViewById(R.id.ibCloseRefHint); ibCloseRefHint = view.findViewById(R.id.ibCloseRefHint);
ibReferenceDelete = view.findViewById(R.id.ibReferenceDelete);
ibReferenceEdit = view.findViewById(R.id.ibReferenceEdit); ibReferenceEdit = view.findViewById(R.id.ibReferenceEdit);
ibReferenceImages = view.findViewById(R.id.ibReferenceImages); ibReferenceImages = view.findViewById(R.id.ibReferenceImages);
style_bar = view.findViewById(R.id.style_bar); style_bar = view.findViewById(R.id.style_bar);
@ -443,13 +438,6 @@ public class FragmentCompose extends FragmentBase {
} }
}); });
ibReferenceDelete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onReferenceDelete();
}
});
ibReferenceEdit.setOnClickListener(new View.OnClickListener() { ibReferenceEdit.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -547,7 +535,6 @@ public class FragmentCompose extends FragmentBase {
grpBody.setVisibility(View.GONE); grpBody.setVisibility(View.GONE);
grpSignature.setVisibility(View.GONE); grpSignature.setVisibility(View.GONE);
grpReferenceHint.setVisibility(View.GONE); grpReferenceHint.setVisibility(View.GONE);
ibReferenceDelete.setVisibility(View.GONE);
ibReferenceEdit.setVisibility(View.GONE); ibReferenceEdit.setVisibility(View.GONE);
ibReferenceImages.setVisibility(View.GONE); ibReferenceImages.setVisibility(View.GONE);
tvReference.setVisibility(View.GONE); tvReference.setVisibility(View.GONE);
@ -663,77 +650,12 @@ public class FragmentCompose extends FragmentBase {
return view; return view;
} }
private void onReferenceDelete() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
if (prefs.getBoolean("delete_ref_confirmed", false)) {
onReferenceDeleteConfirmed();
return;
}
Bundle args = new Bundle();
args.putString("question", getString(R.string.title_ask_delete_ref));
args.putString("notagain", "delete_ref_confirmed");
FragmentDialogAsk fragment = new FragmentDialogAsk();
fragment.setArguments(args);
fragment.setTargetFragment(this, REQUEST_REF_DELETE);
fragment.show(getParentFragmentManager(), "compose:refdelete");
}
private void onReferenceDeleteConfirmed() {
Bundle args = new Bundle();
args.putLong("id", working);
new SimpleTask<EntityMessage>() {
@Override
protected void onPreExecute(Bundle args) {
ibReferenceDelete.setEnabled(false);
ibReferenceEdit.setEnabled(false);
}
@Override
protected void onPostExecute(Bundle args) {
ibReferenceDelete.setEnabled(true);
ibReferenceEdit.setEnabled(true);
}
@Override
protected EntityMessage onExecute(Context context, Bundle args) throws Throwable {
long id = args.getLong("id");
DB db = DB.getInstance(context);
EntityMessage draft = db.message().getMessage(id);
if (draft != null) {
File refFile = draft.getRefFile(context);
refFile.delete();
}
return draft;
}
@Override
protected void onExecuted(Bundle args, EntityMessage draft) {
if (draft != null) {
tvReference.setVisibility(View.GONE);
grpReferenceHint.setVisibility(View.GONE);
ibReferenceDelete.setVisibility(View.GONE);
ibReferenceEdit.setVisibility(View.GONE);
ibReferenceImages.setVisibility(View.GONE);
}
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(this, args, "compose:refdelete");
}
private void onReferenceEdit() { private void onReferenceEdit() {
PopupMenuLifecycle popupMenu = new PopupMenuLifecycle(getContext(), getViewLifecycleOwner(), ibReferenceEdit); PopupMenuLifecycle popupMenu = new PopupMenuLifecycle(getContext(), getViewLifecycleOwner(), ibReferenceEdit);
popupMenu.getMenu().add(Menu.NONE, R.string.title_edit_plain_text, 1, R.string.title_edit_plain_text); popupMenu.getMenu().add(Menu.NONE, R.string.title_edit_plain_text, 1, R.string.title_edit_plain_text);
popupMenu.getMenu().add(Menu.NONE, R.string.title_edit_formatted_text, 2, R.string.title_edit_formatted_text); popupMenu.getMenu().add(Menu.NONE, R.string.title_edit_formatted_text, 2, R.string.title_edit_formatted_text);
popupMenu.getMenu().add(Menu.NONE, R.string.title_delete, 3, R.string.title_delete);
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override @Override
@ -747,6 +669,10 @@ public class FragmentCompose extends FragmentBase {
convertRef(false); convertRef(false);
return true; return true;
case R.string.title_delete:
deleteRef();
return true;
default: default:
return false; return false;
} }
@ -761,13 +687,11 @@ public class FragmentCompose extends FragmentBase {
new SimpleTask<String>() { new SimpleTask<String>() {
@Override @Override
protected void onPreExecute(Bundle args) { protected void onPreExecute(Bundle args) {
ibReferenceDelete.setEnabled(false);
ibReferenceEdit.setEnabled(false); ibReferenceEdit.setEnabled(false);
} }
@Override @Override
protected void onPostExecute(Bundle args) { protected void onPostExecute(Bundle args) {
ibReferenceDelete.setEnabled(true);
ibReferenceEdit.setEnabled(true); ibReferenceEdit.setEnabled(true);
} }
@ -777,16 +701,27 @@ public class FragmentCompose extends FragmentBase {
boolean plain = args.getBoolean("plain"); boolean plain = args.getBoolean("plain");
String body = args.getString("body"); String body = args.getString("body");
String html; Document doc = JsoupEx.parse(Helper.readText(EntityMessage.getFile(context, id)));
File refFile = EntityMessage.getRefFile(context, id); Elements ref = doc.select("div[fairemail=reference]");
String ref = Helper.readText(refFile); ref.removeAttr("fairemail");
Document document = JsoupEx.parse(body);
if (plain) { if (plain) {
String text = HtmlHelper.getText(ref); String text = HtmlHelper.getText(ref.outerHtml());
html = "<p>" + text.replaceAll("\\r?\\n", "<br>") + "</p>"; Element p = document.createElement("p");
} else p.html(text.replaceAll("\\r?\\n", "<br>"));
html = HtmlHelper.sanitize(context, ref, true, false); if (document.body() != null)
document.body().appendChild(p);
} else {
Document d = HtmlHelper.sanitize(context, ref.outerHtml(), true, false);
Element b = d.body();
if (document.body() != null && b != null) {
b.tagName("div");
document.body().appendChild(b);
}
}
return body + html; return document.html();
} }
@Override @Override
@ -803,6 +738,13 @@ public class FragmentCompose extends FragmentBase {
} }
}.execute(FragmentCompose.this, args, "compose:convert"); }.execute(FragmentCompose.this, args, "compose:convert");
} }
private void deleteRef() {
Bundle extras = new Bundle();
extras.putString("html", HtmlHelper.toHtml(etBody.getText()));
extras.putBoolean("show", true);
onAction(R.id.action_save, extras);
}
}); });
popupMenu.show(); popupMenu.show();
@ -1316,10 +1258,6 @@ public class FragmentCompose extends FragmentBase {
if (resultCode == RESULT_OK && data != null) if (resultCode == RESULT_OK && data != null)
onPgp(data); onPgp(data);
break; break;
case REQUEST_REF_DELETE:
if (resultCode == RESULT_OK)
onReferenceDeleteConfirmed();
break;
case REQUEST_CONTACT_GROUP: case REQUEST_CONTACT_GROUP:
if (resultCode == RESULT_OK && data != null) if (resultCode == RESULT_OK && data != null)
onContactGroupSelected(data.getBundleExtra("args")); onContactGroupSelected(data.getBundleExtra("args"));
@ -2187,7 +2125,7 @@ public class FragmentCompose extends FragmentBase {
EntityMessage ref = db.message().getMessage(reference); EntityMessage ref = db.message().getMessage(reference);
String body = ""; Document document = JsoupEx.parse("");
data.draft = new EntityMessage(); data.draft = new EntityMessage();
data.draft.msgid = EntityMessage.generateMessageId(); data.draft.msgid = EntityMessage.generateMessageId();
@ -2224,15 +2162,26 @@ public class FragmentCompose extends FragmentBase {
} }
data.draft.subject = args.getString("subject", ""); data.draft.subject = args.getString("subject", "");
body = args.getString("body", ""); String b = args.getString("body", "");
if (!TextUtils.isEmpty(body)) if (!TextUtils.isEmpty(b)) {
body = HtmlHelper.sanitize(context, body, false, false); Document d = HtmlHelper.sanitize(context, b, false, false);
Element e = d.body();
if (e != null) {
e.tagName("div");
document.body().appendChild(e);
}
}
if (answer > 0) { if (answer > 0) {
EntityAnswer a = db.answer().getAnswer(answer); EntityAnswer a = db.answer().getAnswer(answer);
if (a != null) { if (a != null) {
data.draft.subject = a.name; data.draft.subject = a.name;
body = a.getText(null) + body; Document d = JsoupEx.parse(a.getText(null));
Element e = d.body();
if (e != null) {
e.tagName("div");
document.body().appendChild(e);
}
} }
} }
} else { } else {
@ -2300,20 +2249,31 @@ public class FragmentCompose extends FragmentBase {
data.draft.subject = ref.subject; data.draft.subject = ref.subject;
if (ref.content) { if (ref.content) {
String html = Helper.readText(ref.getFile(context)); String html = Helper.readText(ref.getFile(context));
body = HtmlHelper.sanitize(context, html, true, false); Document d = HtmlHelper.sanitize(context, html, true, false);
Element e = d.body();
if (e != null) {
e.tagName("div");
document.body().appendChild(e);
}
} }
} else if ("list".equals(action)) { } else if ("list".equals(action)) {
data.draft.subject = ref.subject; data.draft.subject = ref.subject;
} else if ("receipt".equals(action)) { } else if ("receipt".equals(action)) {
data.draft.subject = context.getString(R.string.title_receipt_subject, subject); data.draft.subject = context.getString(R.string.title_receipt_subject, subject);
Configuration configuration = new Configuration(context.getResources().getConfiguration()); Element p = document.createElement("p");
configuration.setLocale(new Locale("en")); p.text(context.getString(R.string.title_receipt_text));
Resources res = context.createConfigurationContext(configuration).getResources(); document.body().appendChild(p);
if (!Locale.getDefault().getLanguage().equals("en")) {
Configuration configuration = new Configuration(context.getResources().getConfiguration());
configuration.setLocale(new Locale("en"));
Resources res = context.createConfigurationContext(configuration).getResources();
body = "<p>" + context.getString(R.string.title_receipt_text) + "</p>"; p = document.createElement("p");
if (!Locale.getDefault().getLanguage().equals("en")) p.text(res.getString(R.string.title_receipt_text));
body += "<p>" + res.getString(R.string.title_receipt_text) + "</p>"; document.body().appendChild(p);
}
} else if ("participation".equals(action)) } else if ("participation".equals(action))
data.draft.subject = status + ": " + ref.subject; data.draft.subject = status + ": " + ref.subject;
@ -2324,8 +2284,93 @@ public class FragmentCompose extends FragmentBase {
if (answer > 0) { if (answer > 0) {
EntityAnswer a = db.answer().getAnswer(answer); EntityAnswer a = db.answer().getAnswer(answer);
if (a != null) if (a != null) {
body = a.getText(data.draft.to) + body; Document d = JsoupEx.parse(a.getText(data.draft.to));
Element e = d.body();
if (e != null) {
e.tagName("div");
document.body().appendChild(e);
}
}
}
if (ref.content &&
!"editasnew".equals(action) &&
!"list".equals(action) &&
!"receipt".equals(action)) {
// Reply/forward
Element div = document.createElement("div");
div.attr("fairemail", "reference");
// Build reply header
Element p = document.createElement("p");
DateFormat DF = Helper.getDateTimeInstance(context);
boolean extended_reply = prefs.getBoolean("extended_reply", false);
if (extended_reply) {
if (ref.from != null && ref.from.length > 0) {
Element strong = document.createElement("strong");
strong.text(context.getString(R.string.title_from) + " ");
p.appendChild(strong);
p.appendText(MessageHelper.formatAddresses(ref.from));
p.appendElement("br");
}
if (ref.to != null && ref.to.length > 0) {
Element strong = document.createElement("strong");
strong.text(context.getString(R.string.title_to) + " ");
p.appendChild(strong);
p.appendText(MessageHelper.formatAddresses(ref.to));
p.appendElement("br");
}
if (ref.cc != null && ref.cc.length > 0) {
Element strong = document.createElement("strong");
strong.text(context.getString(R.string.title_cc) + " ");
p.appendChild(strong);
p.appendText(MessageHelper.formatAddresses(ref.cc));
p.appendElement("br");
}
{
Element strong = document.createElement("strong");
strong.text(context.getString(R.string.title_received) + " ");
p.appendChild(strong);
p.appendText(DF.format(ref.received));
p.appendElement("br");
}
{
Element strong = document.createElement("strong");
strong.text(context.getString(R.string.title_subject) + " ");
p.appendChild(strong);
p.appendText(ref.subject == null ? "" : ref.subject);
p.appendElement("br");
}
} else
p.text(DF.format(new Date(ref.received)) + " " + MessageHelper.formatAddresses(ref.from) + ":");
div.appendChild(p);
// Get referenced message body
Document d = JsoupEx.parse(Helper.readText(ref.getFile(context)));
// Remove signature separators
boolean usenet = prefs.getBoolean("usenet_signature", false);
if (usenet)
for (Element span : d.select("span"))
if (span.childNodeSize() == 2 &&
span.childNode(0) instanceof TextNode &&
"-- ".equals(span.wholeText()) &&
"br".equals(span.childNode(1).nodeName()))
span.remove();
// Quote referenced message body
Element e = d.body();
if (e != null) {
boolean quote_reply = prefs.getBoolean("quote_reply", true);
boolean quote = (quote_reply && ("reply".equals(action) || "reply_all".equals(action)));
e.tagName(quote ? "blockquote" : "div");
div.appendChild(e);
}
document.body().appendChild(div);
} }
} }
@ -2442,12 +2487,15 @@ public class FragmentCompose extends FragmentBase {
data.draft.revisions = 1; data.draft.revisions = 1;
data.draft.id = db.message().insertMessage(data.draft); data.draft.id = db.message().insertMessage(data.draft);
Helper.writeText(data.draft.getFile(context), body);
String html = document.html();
Helper.writeText(data.draft.getFile(context), html);
Helper.writeText(data.draft.getFile(context, data.draft.revision), html);
db.message().setMessageContent(data.draft.id, db.message().setMessageContent(data.draft.id,
true, true,
data.draft.plain_only, data.draft.plain_only,
HtmlHelper.getPreview(body), HtmlHelper.getPreview(html),
null); null);
if ("participation".equals(action)) { if ("participation".equals(action)) {
@ -2464,87 +2512,6 @@ public class FragmentCompose extends FragmentBase {
ics.renameTo(attachment.getFile(context)); ics.renameTo(attachment.getFile(context));
} }
// Write reference text
if (ref != null && ref.content &&
!"editasnew".equals(action) &&
!"list".equals(action) &&
!"receipt".equals(action)) {
String refText = Helper.readText(ref.getFile(context));
boolean usenet = prefs.getBoolean("usenet_signature", false);
if (usenet) {
Document rdoc = JsoupEx.parse(refText);
List<Node> tbd = new ArrayList<>();
NodeTraversor.traverse(new NodeVisitor() {
boolean found = false;
public void head(Node node, int depth) {
if (node instanceof TextNode &&
"-- ".equals(((TextNode) node).getWholeText()) &&
node.nextSibling() != null &&
"br".equals(node.nextSibling().nodeName()))
found = true;
if (found)
tbd.add(node);
}
public void tail(Node node, int depth) {
// Do nothing
}
}, rdoc);
if (tbd.size() > 0) {
for (Node node : tbd)
node.remove();
refText = (rdoc.body() == null ? "" : rdoc.body().html());
}
}
// Build reply header
StringBuilder sb = new StringBuilder();
DateFormat DF = Helper.getDateTimeInstance(context);
boolean extended_reply = prefs.getBoolean("extended_reply", false);
if (extended_reply) {
sb.append("<p>");
if (ref.from != null && ref.from.length > 0)
sb.append("<strong>").append(context.getString(R.string.title_from)).append("</strong> ")
.append(Html.escapeHtml(MessageHelper.formatAddresses(ref.from)))
.append("<br>\n");
if (ref.to != null && ref.to.length > 0)
sb.append("<strong>").append(context.getString(R.string.title_to)).append("</strong> ")
.append(Html.escapeHtml(MessageHelper.formatAddresses(ref.to))).
append("<br>\n");
if (ref.cc != null && ref.cc.length > 0)
sb.append("<strong>").append(context.getString(R.string.title_cc)).append("</strong> ")
.append(Html.escapeHtml(MessageHelper.formatAddresses(ref.cc)))
.append("<br>\n");
sb.append("<strong>").append(context.getString(R.string.title_received)).append("</strong> ")
.append(Html.escapeHtml(DF.format(ref.received)))
.append("<br>\n");
sb.append("<strong>").append(context.getString(R.string.title_subject)).append("</strong> ")
.append(Html.escapeHtml(ref.subject == null ? "" : ref.subject));
sb.append("</p>\n");
} else {
sb.append("<p>");
sb.append(Html.escapeHtml(DF.format(new Date(ref.received)))).append(" ");
sb.append(Html.escapeHtml(MessageHelper.formatAddresses(ref.from))).append(":");
sb.append("</p>\n");
}
boolean quote_reply = prefs.getBoolean("quote_reply", true);
boolean quote = (quote_reply && ("reply".equals(action) || "reply_all".equals(action)));
if (quote)
sb.append("<blockquote>");
sb.append(refText);
if (quote)
sb.append("</blockquote>");
Helper.writeText(data.draft.getRefFile(context), sb.toString());
}
if ("new".equals(action)) { if ("new".equals(action)) {
ArrayList<Uri> uris = args.getParcelableArrayList("attachments"); ArrayList<Uri> uris = args.getParcelableArrayList("attachments");
if (uris != null) if (uris != null)
@ -2581,22 +2548,43 @@ public class FragmentCompose extends FragmentBase {
} }
} }
// Create initial revision
EntityRevision revision = new EntityRevision();
revision.message = data.draft.id;
revision.sequence = data.draft.revision;
revision.reference = data.draft.getRefFile(context).exists();
db.revision().insertRevision(revision);
Helper.writeText(data.draft.getFile(context, data.draft.revision), body);
if (data.draft.encrypt == null || !data.draft.encrypt) if (data.draft.encrypt == null || !data.draft.encrypt)
EntityOperation.queue(context, data.draft, EntityOperation.ADD); EntityOperation.queue(context, data.draft, EntityOperation.ADD);
} else { } else {
if (data.draft.content) { if (data.draft.content) {
if (data.draft.revision == null) {
data.draft.revision = 1;
data.draft.revisions = 1;
db.message().setMessageRevision(data.draft.id, data.draft.revision);
db.message().setMessageRevisions(data.draft.id, data.draft.revisions);
}
File file = data.draft.getFile(context); File file = data.draft.getFile(context);
String html = Helper.readText(file);
html = HtmlHelper.sanitize(context, html, true, false); Document doc = JsoupEx.parse(Helper.readText(file));
Elements ref = doc.select("div[fairemail=reference]");
ref.remove();
File refFile = data.draft.getRefFile(context);
if (refFile.exists()) {
ref.html(Helper.readText(refFile));
refFile.delete();
}
Document document = HtmlHelper.sanitize(context, doc.html(), true, false);
for (Element e : ref)
document.appendChild(e);
String html = JsoupEx.parse(document.html()).html();
Helper.writeText(file, html); Helper.writeText(file, html);
Helper.writeText(data.draft.getFile(context, data.draft.revision), html);
db.message().setMessageContent(data.draft.id,
true,
data.draft.plain_only,
HtmlHelper.getPreview(html),
null);
} else { } else {
if (data.draft.uid == null) if (data.draft.uid == null)
throw new IllegalStateException("Draft without uid"); throw new IllegalStateException("Draft without uid");
@ -2653,10 +2641,8 @@ public class FragmentCompose extends FragmentBase {
grpAddresses.setVisibility("reply_all".equals(action) ? View.VISIBLE : View.GONE); grpAddresses.setVisibility("reply_all".equals(action) ? View.VISIBLE : View.GONE);
ibCcBcc.setVisibility(View.VISIBLE); ibCcBcc.setVisibility(View.VISIBLE);
bottom_navigation.getMenu().findItem(R.id.action_undo).setVisible( bottom_navigation.getMenu().findItem(R.id.action_undo).setVisible(data.draft.revision > 1);
data.draft.revision != null && data.draft.revision > 1); bottom_navigation.getMenu().findItem(R.id.action_redo).setVisible(data.draft.revision < data.draft.revisions);
bottom_navigation.getMenu().findItem(R.id.action_redo).setVisible(
data.draft.revision != null && !data.draft.revision.equals(data.draft.revisions));
if (args.getBoolean("incomplete")) if (args.getBoolean("incomplete"))
Snackbar.make(view, R.string.title_attachments_incomplete, Snackbar.LENGTH_LONG).show(); Snackbar.make(view, R.string.title_attachments_incomplete, Snackbar.LENGTH_LONG).show();
@ -2956,59 +2942,54 @@ public class FragmentCompose extends FragmentBase {
db.message().updateMessage(draft); db.message().updateMessage(draft);
} }
if (action == R.id.action_undo || action == R.id.action_redo) { String p = Helper.readText(draft.getFile(context));
if (draft.revision != null && draft.revisions != null) { Document doc = JsoupEx.parse(p);
Helper.writeText(draft.getFile(context, draft.revision), body); if ((body != null && !body.equals(doc.html())) ||
(extras != null && extras.containsKey("html"))) {
if (action == R.id.action_undo) { dirty = true;
if (draft.revision > 1)
draft.revision--; Elements ref = doc.select("div[fairemail=reference]");
} else { ref.remove();
if (draft.revision < draft.revisions)
draft.revision++; // Get saved body
} Document d;
if (extras != null && extras.containsKey("html")) {
// Restore revision // Save current revision
Log.i("Restoring revision=" + draft.revision); Document c = JsoupEx.parse(body);
body = Helper.readText(draft.getFile(context, draft.revision)); if (c.body() != null && ref.size() > 0)
c.body().appendChild(ref.first());
Helper.writeText(draft.getFile(context, draft.revision), c.html());
d = JsoupEx.parse(extras.getString("html"));
} else {
d = JsoupEx.parse(body);
if (d.body() != null && ref.size() > 0)
d.body().appendChild(ref.first());
} }
} else {
String previous = Helper.readText(draft.getFile(context));
if ((body != null && !body.equals(previous)) ||
(extras != null && extras.containsKey("html"))) {
dirty = true;
// Get revision info
boolean reference;
if (extras != null && extras.containsKey("html")) {
reference = false;
body = extras.getString("html");
} else {
File refFile = EntityMessage.getRefFile(context, draft.id);
if (draft.revision == null)
reference = refFile.exists();
else {
EntityRevision revision = db.revision().getRevision(draft.id, draft.revision);
reference = (revision == null ? refFile.exists() : revision.reference);
}
}
// Create new revision body = d.html();
if (draft.revisions == null)
draft.revisions = 1; // Create new revision
else if (action != R.id.action_undo && action != R.id.action_redo) {
draft.revisions++; draft.revisions++;
draft.revision = draft.revisions; draft.revision = draft.revisions;
}
Log.i("Creating revision sequence=" + draft.revision + " reference=" + reference); Helper.writeText(draft.getFile(context, draft.revision), body);
EntityRevision revision = new EntityRevision(); }
revision.message = draft.id;
revision.sequence = draft.revision;
revision.reference = reference;
db.revision().insertRevision(revision);
Helper.writeText(draft.getFile(context, draft.revision), body); if (action == R.id.action_undo || action == R.id.action_redo) {
if (action == R.id.action_undo) {
if (draft.revision > 1)
draft.revision--;
} else {
if (draft.revision < draft.revisions)
draft.revision++;
} }
// Restore revision
Log.i("Restoring revision=" + draft.revision);
body = Helper.readText(draft.getFile(context, draft.revision));
} }
Helper.writeText(draft.getFile(context), body); Helper.writeText(draft.getFile(context), body);
@ -3094,13 +3075,8 @@ public class FragmentCompose extends FragmentBase {
} else if (action == R.id.action_send) { } else if (action == R.id.action_send) {
// Remove unused inline images // Remove unused inline images
StringBuilder sb = new StringBuilder();
sb.append(body);
File rfile = draft.getRefFile(context);
if (rfile.exists())
sb.append(Helper.readText(rfile));
List<String> cids = new ArrayList<>(); List<String> cids = new ArrayList<>();
for (Element element : JsoupEx.parse(sb.toString()).select("img")) { for (Element element : JsoupEx.parse(body).select("img")) {
String src = element.attr("src"); String src = element.attr("src");
if (src.startsWith("cid:")) if (src.startsWith("cid:"))
cids.add("<" + src.substring(4) + ">"); cids.add("<" + src.substring(4) + ">");
@ -3115,8 +3091,6 @@ public class FragmentCompose extends FragmentBase {
// Delete draft (cannot move to outbox) // Delete draft (cannot move to outbox)
EntityOperation.queue(context, draft, EntityOperation.DELETE); EntityOperation.queue(context, draft, EntityOperation.DELETE);
File refDraftFile = draft.getRefFile(context);
// Copy message to outbox // Copy message to outbox
draft.id = null; draft.id = null;
draft.folder = db.folder().getOutbox().id; draft.folder = db.folder().getOutbox().id;
@ -3124,10 +3098,6 @@ public class FragmentCompose extends FragmentBase {
draft.ui_hide = false; draft.ui_hide = false;
draft.id = db.message().insertMessage(draft); draft.id = db.message().insertMessage(draft);
Helper.writeText(draft.getFile(context), body); Helper.writeText(draft.getFile(context), body);
if (refDraftFile.exists()) {
File refFile = draft.getRefFile(context);
refDraftFile.renameTo(refFile);
}
// Move attachments // Move attachments
for (EntityAttachment attachment : attachments) for (EntityAttachment attachment : attachments)
@ -3183,8 +3153,8 @@ public class FragmentCompose extends FragmentBase {
etCc.setText(MessageHelper.formatAddressesCompose(draft.cc)); etCc.setText(MessageHelper.formatAddressesCompose(draft.cc));
etBcc.setText(MessageHelper.formatAddressesCompose(draft.bcc)); etBcc.setText(MessageHelper.formatAddressesCompose(draft.bcc));
bottom_navigation.getMenu().findItem(R.id.action_undo).setVisible(draft.revision != null && draft.revision > 1); bottom_navigation.getMenu().findItem(R.id.action_undo).setVisible(draft.revision > 1);
bottom_navigation.getMenu().findItem(R.id.action_redo).setVisible(draft.revision != null && !draft.revision.equals(draft.revisions)); bottom_navigation.getMenu().findItem(R.id.action_redo).setVisible(draft.revision < draft.revisions);
if (action == R.id.action_delete) { if (action == R.id.action_delete) {
autosave = false; autosave = false;
@ -3289,8 +3259,8 @@ public class FragmentCompose extends FragmentBase {
pbWait.setVisibility(View.GONE); pbWait.setVisibility(View.GONE);
media_bar.setVisibility(media ? View.VISIBLE : View.GONE); media_bar.setVisibility(media ? View.VISIBLE : View.GONE);
bottom_navigation.getMenu().findItem(R.id.action_undo).setVisible(draft.revision != null && draft.revision > 1); bottom_navigation.getMenu().findItem(R.id.action_undo).setVisible(draft.revision > 1);
bottom_navigation.getMenu().findItem(R.id.action_redo).setVisible(draft.revision != null && !draft.revision.equals(draft.revisions)); bottom_navigation.getMenu().findItem(R.id.action_redo).setVisible(draft.revision < draft.revisions);
bottom_navigation.setVisibility(View.VISIBLE); bottom_navigation.setVisibility(View.VISIBLE);
Helper.setViewsEnabled(view, true); Helper.setViewsEnabled(view, true);
@ -3310,8 +3280,11 @@ public class FragmentCompose extends FragmentBase {
if (draft == null || !draft.content) if (draft == null || !draft.content)
throw new IllegalArgumentException(context.getString(R.string.title_no_body)); throw new IllegalArgumentException(context.getString(R.string.title_no_body));
String body = Helper.readText(draft.getFile(context)); Document doc = JsoupEx.parse(Helper.readText(draft.getFile(context)));
Spanned spannedBody = HtmlHelper.fromHtml(body, new Html.ImageGetter() { Elements ref = doc.select("div[fairemail=reference]");
ref.remove();
Spanned spannedBody = HtmlHelper.fromHtml(doc.html(), new Html.ImageGetter() {
@Override @Override
public Drawable getDrawable(String source) { public Drawable getDrawable(String source) {
return ImageHelper.decodeImage(context, id, source, true, zoom, etBody); return ImageHelper.decodeImage(context, id, source, true, zoom, etBody);
@ -3332,13 +3305,9 @@ public class FragmentCompose extends FragmentBase {
spannedBody = bodyBuilder; spannedBody = bodyBuilder;
Spanned spannedRef = null; Spanned spannedRef = null;
File refFile = draft.getRefFile(context); if (!ref.isEmpty()) {
EntityRevision revision = db.revision().getRevision(draft.id, draft.revision == null ? 0 : draft.revision); Document quote = HtmlHelper.sanitize(context, ref.outerHtml(), show_images, false);
Log.i("Viewing revision=" + (revision == null ? null : revision.sequence) + Spanned spannedQuote = HtmlHelper.fromHtml(quote.html(),
" reference=" + (revision == null ? null : revision.reference));
if (revision == null ? refFile.exists() : revision.reference) {
String quote = HtmlHelper.sanitize(context, Helper.readText(refFile), show_images, false);
Spanned spannedQuote = HtmlHelper.fromHtml(quote,
new Html.ImageGetter() { new Html.ImageGetter() {
@Override @Override
public Drawable getDrawable(String source) { public Drawable getDrawable(String source) {
@ -3384,7 +3353,6 @@ public class FragmentCompose extends FragmentBase {
tvReference.setText(text[1]); tvReference.setText(text[1]);
tvReference.setVisibility(text[1] == null ? View.GONE : View.VISIBLE); tvReference.setVisibility(text[1] == null ? View.GONE : View.VISIBLE);
grpReferenceHint.setVisibility(text[1] == null || !ref_hint ? View.GONE : View.VISIBLE); grpReferenceHint.setVisibility(text[1] == null || !ref_hint ? View.GONE : View.VISIBLE);
ibReferenceDelete.setVisibility(text[1] == null ? View.GONE : View.VISIBLE);
ibReferenceEdit.setVisibility(text[1] == null ? View.GONE : View.VISIBLE); ibReferenceEdit.setVisibility(text[1] == null ? View.GONE : View.VISIBLE);
ibReferenceImages.setVisibility(ref_has_images && !show_images ? View.VISIBLE : View.GONE); ibReferenceImages.setVisibility(ref_has_images && !show_images ? View.VISIBLE : View.GONE);

@ -69,7 +69,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
private final static String[] RESET_QUESTIONS = new String[]{ private final static String[] RESET_QUESTIONS = new String[]{
"welcome", "crash_reports_asked", "welcome", "crash_reports_asked",
"html_always_images", "print_html_confirmed", "html_always_images", "print_html_confirmed",
"identities_asked", "delete_ref_confirmed", "send_dialog" "identities_asked", "compose_reference", "send_dialog"
}; };
@Override @Override

@ -80,17 +80,21 @@ public class HtmlHelper {
private static final List<String> tails = Collections.unmodifiableList(Arrays.asList( private static final List<String> tails = Collections.unmodifiableList(Arrays.asList(
"h1", "h2", "h3", "h4", "h5", "h6", "p", "ol", "ul", "li")); "h1", "h2", "h3", "h4", "h5", "h6", "p", "ol", "ul", "li"));
static String sanitize(Context context, String html, boolean show_images, boolean autolink) { static Document sanitize(Context context, String html, boolean show_images, boolean autolink) {
try { try {
return _sanitize(context, html, show_images, autolink); return _sanitize(context, html, show_images, autolink);
} catch (Throwable ex) { } catch (Throwable ex) {
// OutOfMemoryError // OutOfMemoryError
Log.e(ex); Log.e(ex);
return Helper.formatThrowable(ex); Document document = new Document("");
Element strong = document.createElement("strong");
strong.text(Helper.formatThrowable(ex));
document.appendChild(strong);
return document;
} }
} }
private static String _sanitize(Context context, String html, boolean show_images, boolean autolink) { private static Document _sanitize(Context context, String html, boolean show_images, boolean autolink) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean text_color = prefs.getBoolean("text_color", true); boolean text_color = prefs.getBoolean("text_color", true);
boolean display_hidden = prefs.getBoolean("display_hidden", false); boolean display_hidden = prefs.getBoolean("display_hidden", false);
@ -472,8 +476,7 @@ public class HtmlHelper {
if (!TextUtils.isEmpty(span.attr("color"))) if (!TextUtils.isEmpty(span.attr("color")))
span.tagName("font"); span.tagName("font");
Element body = document.body(); return document;
return (body == null ? "" : body.html());
} }
private static boolean hasVisibleContent(List<Node> nodes) { private static boolean hasVisibleContent(List<Node> nodes) {

@ -31,6 +31,8 @@ import com.sun.mail.util.FolderClosedIOException;
import com.sun.mail.util.MessageRemovedIOException; import com.sun.mail.util.MessageRemovedIOException;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
@ -284,9 +286,6 @@ public class MessageHelper {
} }
static void build(Context context, EntityMessage message, List<EntityAttachment> attachments, EntityIdentity identity, MimeMessage imessage) throws IOException, MessagingException { static void build(Context context, EntityMessage message, List<EntityAttachment> attachments, EntityIdentity identity, MimeMessage imessage) throws IOException, MessagingException {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean usenet = prefs.getBoolean("usenet_signature", false);
if (message.receipt != null && message.receipt) { if (message.receipt != null && message.receipt) {
// https://www.ietf.org/rfc/rfc3798.txt // https://www.ietf.org/rfc/rfc3798.txt
Multipart report = new MimeMultipart("report; report-type=disposition-notification"); Multipart report = new MimeMultipart("report; report-type=disposition-notification");
@ -322,31 +321,30 @@ public class MessageHelper {
} }
// Build html body // Build html body
StringBuilder body = new StringBuilder(); Document document = JsoupEx.parse(Helper.readText(message.getFile(context)));
body.append("<html><body>"); Elements ref = document.select("div[fairemail=reference]");
ref.remove();
Document mdoc = JsoupEx.parse(Helper.readText(message.getFile(context))); ref.removeAttr("fairemail");
if (mdoc.body() != null)
body.append(mdoc.body().html()); if (document.body() != null) {
// When sending message
// When sending message if (identity != null && !TextUtils.isEmpty(identity.signature) && message.signature) {
if (identity != null) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (!TextUtils.isEmpty(identity.signature) && message.signature) { boolean usenet = prefs.getBoolean("usenet_signature", false);
Document sdoc = JsoupEx.parse(identity.signature); if (usenet) {
if (sdoc.body() != null) { // https://www.ietf.org/rfc/rfc3676.txt
if (usenet) // https://www.ietf.org/rfc/rfc3676.txt Element span = document.createElement("span");
body.append("<span>-- <br></span>"); span.text("-- ");
body.append(sdoc.body().html()); span.appendElement("br");
document.body().appendChild(span);
} }
document.body().append(identity.signature);
} }
File refFile = message.getRefFile(context); if (ref.size() > 0)
if (refFile.exists()) document.body().appendChild(ref.first());
body.append(Helper.readText(refFile));
} }
body.append("</body></html>");
// multipart/mixed // multipart/mixed
// multipart/related // multipart/related
// multipart/alternative // multipart/alternative
@ -355,7 +353,7 @@ public class MessageHelper {
// inlines // inlines
// attachments // attachments
String htmlContent = body.toString(); String htmlContent = document.html();
String plainContent = HtmlHelper.getText(htmlContent); String plainContent = HtmlHelper.getText(htmlContent);
BodyPart plainPart = new MimeBodyPart(); BodyPart plainPart = new MimeBodyPart();

@ -339,23 +339,10 @@
android:text="@string/title_no_format" android:text="@string/title_no_format"
android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textStyle="italic" android:textStyle="italic"
app:layout_constraintEnd_toStartOf="@+id/ibReferenceDelete" app:layout_constraintEnd_toStartOf="@+id/ibReferenceEdit"
app:layout_constraintStart_toEndOf="@id/ibCloseRefHint" app:layout_constraintStart_toEndOf="@id/ibCloseRefHint"
app:layout_constraintTop_toBottomOf="@id/vSeparatorSignature" /> app:layout_constraintTop_toBottomOf="@id/vSeparatorSignature" />
<ImageButton
android:id="@+id/ibReferenceDelete"
android:layout_width="36dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:layout_marginEnd="6dp"
android:background="@null"
android:contentDescription="@string/title_legend_edit"
android:padding="3dp"
app:layout_constraintEnd_toStartOf="@+id/ibReferenceEdit"
app:layout_constraintTop_toBottomOf="@id/vSeparatorSignature"
app:srcCompat="@drawable/baseline_delete_24" />
<ImageButton <ImageButton
android:id="@+id/ibReferenceEdit" android:id="@+id/ibReferenceEdit"
android:layout_width="36dp" android:layout_width="36dp"
@ -387,7 +374,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:barrierDirection="bottom" app:barrierDirection="bottom"
app:constraint_referenced_ids="ibCloseRefHint,tvReferenceHint,ibReferenceDelete,ibReferenceEdit,ibReferenceImages" /> app:constraint_referenced_ids="ibCloseRefHint,tvReferenceHint,ibReferenceEdit,ibReferenceImages" />
<TextView <TextView
android:id="@+id/tvReference" android:id="@+id/tvReference"

@ -623,7 +623,6 @@
<string name="title_ask_show_html_images">Always show images on showing original messages</string> <string name="title_ask_show_html_images">Always show images on showing original messages</string>
<string name="title_ask_show_image">Showing images can leak privacy sensitive information</string> <string name="title_ask_show_image">Showing images can leak privacy sensitive information</string>
<string name="title_ask_show_image_hint">Images recognized as tracking images will not be shown</string> <string name="title_ask_show_image_hint">Images recognized as tracking images will not be shown</string>
<string name="title_ask_delete_ref">Delete replied/forwarded message text? This cannot be undone.</string>
<string name="title_ask_sync_all">Synchronize all messages in %1$s?</string> <string name="title_ask_sync_all">Synchronize all messages in %1$s?</string>
<string name="title_ask_delete_local">Delete local messages? Messages will remain on the remote server.</string> <string name="title_ask_delete_local">Delete local messages? Messages will remain on the remote server.</string>
<string name="title_ask_help">Help improve FairEmail</string> <string name="title_ask_help">Help improve FairEmail</string>

Loading…
Cancel
Save