mirror of
https://github.com/nextcloud/notes-android.git
synced 2024-11-22 04:46:04 +03:00
support www links without protocol in preview mode (resolve #949)
This commit is contained in:
parent
2d1e4e5b60
commit
7767776835
9 changed files with 230 additions and 58 deletions
|
@ -35,9 +35,13 @@ import com.yydcdut.markdown.syntax.text.TextFactory;
|
|||
import it.niedermann.owncloud.notes.R;
|
||||
import it.niedermann.owncloud.notes.databinding.FragmentNotePreviewBinding;
|
||||
import it.niedermann.owncloud.notes.persistence.NotesDatabase;
|
||||
import it.niedermann.owncloud.notes.shared.model.DBNote;
|
||||
import it.niedermann.owncloud.notes.shared.util.MarkDownUtil;
|
||||
import it.niedermann.owncloud.notes.shared.util.NoteLinksUtils;
|
||||
import it.niedermann.owncloud.notes.shared.util.SSOUtil;
|
||||
import it.niedermann.owncloud.notes.shared.util.text.NoteLinksProcessor;
|
||||
import it.niedermann.owncloud.notes.shared.util.text.TextProcessorChain;
|
||||
import it.niedermann.owncloud.notes.shared.util.text.WwwLinksProcessor;
|
||||
|
||||
import static it.niedermann.owncloud.notes.shared.util.DisplayUtils.searchAndColor;
|
||||
import static it.niedermann.owncloud.notes.shared.util.MarkDownUtil.CHECKBOX_CHECKED_MINUS;
|
||||
|
@ -46,7 +50,6 @@ import static it.niedermann.owncloud.notes.shared.util.MarkDownUtil.CHECKBOX_UNC
|
|||
import static it.niedermann.owncloud.notes.shared.util.MarkDownUtil.CHECKBOX_UNCHECKED_STAR;
|
||||
import static it.niedermann.owncloud.notes.shared.util.MarkDownUtil.parseCompat;
|
||||
import static it.niedermann.owncloud.notes.shared.util.NoteLinksUtils.extractNoteRemoteId;
|
||||
import static it.niedermann.owncloud.notes.shared.util.NoteLinksUtils.replaceNoteLinksWithDummyUrls;
|
||||
import static it.niedermann.owncloud.notes.shared.util.NoteUtil.getFontSizeFromPreferences;
|
||||
|
||||
public class NotePreviewFragment extends SearchableBaseNoteFragment implements OnRefreshListener {
|
||||
|
@ -165,11 +168,13 @@ public class NotePreviewFragment extends SearchableBaseNoteFragment implements O
|
|||
}
|
||||
})
|
||||
.build());
|
||||
|
||||
TextProcessorChain chain = defaultTextProcessorChain(note);
|
||||
try {
|
||||
binding.singleNoteContent.setText(parseCompat(markdownProcessor, replaceNoteLinksWithDummyUrls(note.getContent(), db.getRemoteIds(note.getAccountId()))));
|
||||
binding.singleNoteContent.setText(parseCompat(markdownProcessor, chain.apply(note.getContent())));
|
||||
} catch (StringIndexOutOfBoundsException e) {
|
||||
// Workaround for RxMarkdown: https://github.com/stefan-niedermann/nextcloud-notes/issues/668
|
||||
binding.singleNoteContent.setText(replaceNoteLinksWithDummyUrls(note.getContent(), db.getRemoteIds(note.getAccountId())));
|
||||
binding.singleNoteContent.setText(chain.apply(note.getContent()));
|
||||
Toast.makeText(binding.singleNoteContent.getContext(), R.string.could_not_load_preview_two_digit_numbered_list, Toast.LENGTH_LONG).show();
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
@ -205,11 +210,12 @@ public class NotePreviewFragment extends SearchableBaseNoteFragment implements O
|
|||
if (db.getNoteServerSyncHelper().isSyncPossible() && SSOUtil.isConfigured(getContext())) {
|
||||
binding.swiperefreshlayout.setRefreshing(true);
|
||||
try {
|
||||
TextProcessorChain chain = defaultTextProcessorChain(note);
|
||||
SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(requireContext());
|
||||
db.getNoteServerSyncHelper().addCallbackPull(ssoAccount, () -> {
|
||||
note = db.getNote(note.getAccountId(), note.getId());
|
||||
changedText = note.getContent();
|
||||
binding.singleNoteContent.setText(parseCompat(markdownProcessor, replaceNoteLinksWithDummyUrls(note.getContent(), db.getRemoteIds(note.getAccountId()))));
|
||||
binding.singleNoteContent.setText(parseCompat(markdownProcessor, chain.apply(note.getContent())));
|
||||
binding.swiperefreshlayout.setRefreshing(false);
|
||||
});
|
||||
db.getNoteServerSyncHelper().scheduleSync(ssoAccount, false);
|
||||
|
@ -227,4 +233,11 @@ public class NotePreviewFragment extends SearchableBaseNoteFragment implements O
|
|||
super.applyBrand(mainColor, textColor);
|
||||
binding.singleNoteContent.setHighlightColor(getTextHighlightBackgroundColor(requireContext(), mainColor, colorPrimary, colorAccent));
|
||||
}
|
||||
|
||||
private TextProcessorChain defaultTextProcessorChain(DBNote note) {
|
||||
TextProcessorChain chain = new TextProcessorChain();
|
||||
chain.add(new NoteLinksProcessor(db.getRemoteIds(note.getAccountId())));
|
||||
chain.add(new WwwLinksProcessor());
|
||||
return chain;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,66 +1,26 @@
|
|||
package it.niedermann.owncloud.notes.shared.util;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import it.niedermann.owncloud.notes.shared.util.text.NoteLinksProcessor;
|
||||
|
||||
public class NoteLinksUtils {
|
||||
|
||||
@VisibleForTesting
|
||||
static final String RELATIVE_LINK_WORKAROUND_PREFIX = "https://nextcloudnotes/notes/";
|
||||
|
||||
private static final String linksThatLookLikeNoteLinksRegEx = "\\[[^]]*]\\((\\d+)\\)";
|
||||
private static final String replaceNoteRemoteIdsRegEx = "\\[([^\\]]*)\\]\\((%s)\\)";
|
||||
|
||||
/**
|
||||
* Replaces all links to other notes of the form `[<link-text>](<note-file-id>)`
|
||||
* in the markdown string with links to a dummy url.
|
||||
*
|
||||
* Why is this needed?
|
||||
* See discussion in issue #623
|
||||
*
|
||||
* @return Markdown with all note-links replaced with dummy-url-links
|
||||
*/
|
||||
public static String replaceNoteLinksWithDummyUrls(String markdown, Set<String> existingNoteRemoteIds) {
|
||||
Pattern noteLinkCandidates = Pattern.compile(linksThatLookLikeNoteLinksRegEx);
|
||||
Matcher matcher = noteLinkCandidates.matcher(markdown);
|
||||
|
||||
Set<String> noteRemoteIdsToReplace = new HashSet<>();
|
||||
while (matcher.find()) {
|
||||
String presumedNoteId = matcher.group(1);
|
||||
if (existingNoteRemoteIds.contains(presumedNoteId)) {
|
||||
noteRemoteIdsToReplace.add(presumedNoteId);
|
||||
}
|
||||
}
|
||||
|
||||
String noteRemoteIdsCondition = TextUtils.join("|", noteRemoteIdsToReplace);
|
||||
Pattern replacePattern = Pattern.compile(String.format(replaceNoteRemoteIdsRegEx, noteRemoteIdsCondition));
|
||||
Matcher replaceMatcher = replacePattern.matcher(markdown);
|
||||
return replaceMatcher.replaceAll(String.format("[$1](%s$2)", RELATIVE_LINK_WORKAROUND_PREFIX));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the given link is a note-link (which was transformed in {@link #replaceNoteLinksWithDummyUrls}) or not
|
||||
* Tests if the given link is a note-link (which was transformed in {@link it.niedermann.owncloud.notes.shared.util.text.NoteLinksProcessor}) or not
|
||||
*
|
||||
* @param link Link under test
|
||||
* @return true if the link is a note-link
|
||||
*/
|
||||
public static boolean isNoteLink(String link) {
|
||||
return link.startsWith(RELATIVE_LINK_WORKAROUND_PREFIX);
|
||||
return link.startsWith(NoteLinksProcessor.RELATIVE_LINK_WORKAROUND_PREFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the remoteId back from links that were transformed in {@link #replaceNoteLinksWithDummyUrls}.
|
||||
* Extracts the remoteId back from links that were transformed in {@link it.niedermann.owncloud.notes.shared.util.text.NoteLinksProcessor}.
|
||||
*
|
||||
* @param link Link that was transformed in {@link #replaceNoteLinksWithDummyUrls}
|
||||
* @param link Link that was transformed in {@link it.niedermann.owncloud.notes.shared.util.text.NoteLinksProcessor}
|
||||
* @return the remoteId of the linked note
|
||||
*/
|
||||
public static long extractNoteRemoteId(String link) {
|
||||
return Long.parseLong(link.replace(RELATIVE_LINK_WORKAROUND_PREFIX, ""));
|
||||
return Long.parseLong(link.replace(NoteLinksProcessor.RELATIVE_LINK_WORKAROUND_PREFIX, ""));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
package it.niedermann.owncloud.notes.shared.util.text;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
public class NoteLinksProcessor extends TextProcessor {
|
||||
|
||||
public static final String RELATIVE_LINK_WORKAROUND_PREFIX = "https://nextcloudnotes/notes/";
|
||||
|
||||
@VisibleForTesting
|
||||
private static final String linksThatLookLikeNoteLinksRegEx = "\\[[^]]*]\\((\\d+)\\)";
|
||||
private static final String replaceNoteRemoteIdsRegEx = "\\[([^\\]]*)\\]\\((%s)\\)";
|
||||
|
||||
private Set<String> existingNoteRemoteIds;
|
||||
|
||||
public NoteLinksProcessor(Set<String> existingNoteRemoteIds) {
|
||||
this.existingNoteRemoteIds = existingNoteRemoteIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces all links to other notes of the form `[<link-text>](<note-file-id>)`
|
||||
* in the markdown string with links to a dummy url.
|
||||
*
|
||||
* Why is this needed?
|
||||
* See discussion in issue #623
|
||||
*
|
||||
* @return Markdown with all note-links replaced with dummy-url-links
|
||||
*/
|
||||
@Override
|
||||
public String process(String s) {
|
||||
return replaceNoteLinksWithDummyUrls(s, existingNoteRemoteIds);
|
||||
}
|
||||
|
||||
private static String replaceNoteLinksWithDummyUrls(String markdown, Set<String> existingNoteRemoteIds) {
|
||||
Pattern noteLinkCandidates = Pattern.compile(linksThatLookLikeNoteLinksRegEx);
|
||||
Matcher matcher = noteLinkCandidates.matcher(markdown);
|
||||
|
||||
Set<String> noteRemoteIdsToReplace = new HashSet<>();
|
||||
while (matcher.find()) {
|
||||
String presumedNoteId = matcher.group(1);
|
||||
if (existingNoteRemoteIds.contains(presumedNoteId)) {
|
||||
noteRemoteIdsToReplace.add(presumedNoteId);
|
||||
}
|
||||
}
|
||||
|
||||
String noteRemoteIdsCondition = TextUtils.join("|", noteRemoteIdsToReplace);
|
||||
Pattern replacePattern = Pattern.compile(String.format(replaceNoteRemoteIdsRegEx, noteRemoteIdsCondition));
|
||||
Matcher replaceMatcher = replacePattern.matcher(markdown);
|
||||
return replaceMatcher.replaceAll(String.format("[$1](%s$2)", RELATIVE_LINK_WORKAROUND_PREFIX));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package it.niedermann.owncloud.notes.shared.util.text;
|
||||
|
||||
abstract public class TextProcessor {
|
||||
/**
|
||||
* Applies a specified transformation on a text string and returns the updated string.
|
||||
* @param s Text to transform
|
||||
* @return Transformed text
|
||||
*/
|
||||
abstract public String process(String s);
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package it.niedermann.owncloud.notes.shared.util.text;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
public class TextProcessorChain extends LinkedList<TextProcessor> {
|
||||
public String apply(String s) {
|
||||
for (TextProcessor textProcessor : this) {
|
||||
s = textProcessor.process(s);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package it.niedermann.owncloud.notes.shared.util.text;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class WwwLinksProcessor extends TextProcessor {
|
||||
|
||||
public static final String WWW_URLS_PROTOCOL_PREFIX = "http://";
|
||||
private static final String replaceWwwUrlsRegEx = "\\[([^]]*)]\\((www\\..+)\\)";
|
||||
|
||||
/**
|
||||
* Prefixes all links, that not not start with a protocol identifier, but with "www." with http://
|
||||
*
|
||||
* See https://github.com/stefan-niedermann/nextcloud-notes/issues/949
|
||||
*
|
||||
* @return Markdown with all pseudo-links replaced through actual HTTP-links
|
||||
*/
|
||||
@Override
|
||||
public String process(String s) {
|
||||
return replaceWwwUrls(s);
|
||||
}
|
||||
|
||||
private static String replaceWwwUrls(String markdown) {
|
||||
Pattern replacePattern = Pattern.compile(replaceWwwUrlsRegEx);
|
||||
Matcher replaceMatcher = replacePattern.matcher(markdown);
|
||||
return replaceMatcher.replaceAll(String.format("[$1](%s$2)", WWW_URLS_PROTOCOL_PREFIX));
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package it.niedermann.owncloud.notes.shared.util;
|
||||
package it.niedermann.owncloud.notes.shared.util.text;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
|
@ -8,18 +8,21 @@ import java.util.Collections;
|
|||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static it.niedermann.owncloud.notes.shared.util.NoteLinksUtils.RELATIVE_LINK_WORKAROUND_PREFIX;
|
||||
import static it.niedermann.owncloud.notes.shared.util.text.NoteLinksProcessor.RELATIVE_LINK_WORKAROUND_PREFIX;
|
||||
|
||||
|
||||
public class NoteLinksUtilsTest extends TestCase {
|
||||
public class NoteLinksProcessorTest extends TestCase {
|
||||
|
||||
public void testEmptyString() {
|
||||
TextProcessor sut = new NoteLinksProcessor(Collections.emptySet());
|
||||
|
||||
String markdown = "";
|
||||
String result = NoteLinksUtils.replaceNoteLinksWithDummyUrls(markdown, Collections.emptySet());
|
||||
String result = sut.process(markdown);
|
||||
Assert.assertEquals("", result);
|
||||
}
|
||||
|
||||
public void testDoNotChangeOtherMarkdownElements() {
|
||||
TextProcessor sut = new NoteLinksProcessor(Collections.emptySet());
|
||||
|
||||
//language=md
|
||||
String markdown = "\n" +
|
||||
"# heading \n" +
|
||||
|
@ -37,14 +40,16 @@ public class NoteLinksUtilsTest extends TestCase {
|
|||
"**Everything** else could be in here.\n" +
|
||||
"\n";
|
||||
|
||||
Assert.assertEquals(markdown, NoteLinksUtils.replaceNoteLinksWithDummyUrls(markdown, Collections.emptySet()));
|
||||
Assert.assertEquals(markdown, sut.process(markdown));
|
||||
}
|
||||
|
||||
@SuppressWarnings("MarkdownUnresolvedFileReference")
|
||||
public void testDoNotReplaceNormalLinks() {
|
||||
TextProcessor sut = new NoteLinksProcessor(Collections.singleton("123456"));
|
||||
|
||||
//language=md
|
||||
String markdown = "[normal link](https://example.com) and another [note link](123456)";
|
||||
String result = NoteLinksUtils.replaceNoteLinksWithDummyUrls(markdown, Collections.singleton("123456"));
|
||||
String result = sut.process(markdown);
|
||||
Assert.assertEquals(String.format("[normal link](https://example.com) and another [note link](%s123456)", RELATIVE_LINK_WORKAROUND_PREFIX), result);
|
||||
}
|
||||
|
||||
|
@ -52,9 +57,12 @@ public class NoteLinksUtilsTest extends TestCase {
|
|||
Set<String> remoteIdsOfExistingNotes = new HashSet<>();
|
||||
remoteIdsOfExistingNotes.add("123456");
|
||||
remoteIdsOfExistingNotes.add("321456");
|
||||
|
||||
TextProcessor sut = new NoteLinksProcessor(remoteIdsOfExistingNotes);
|
||||
|
||||
String markdown = "[link to real note](123456) and another [link to non-existing note](654321) and one more [another link to real note](321456)";
|
||||
|
||||
String result = NoteLinksUtils.replaceNoteLinksWithDummyUrls(markdown, remoteIdsOfExistingNotes);
|
||||
String result = sut.process(markdown);
|
||||
|
||||
String expected = String.format("[link to real note](%s123456) and another [link to non-existing note](654321) and one more [another link to real note](%s321456)", RELATIVE_LINK_WORKAROUND_PREFIX, RELATIVE_LINK_WORKAROUND_PREFIX);
|
||||
Assert.assertEquals(
|
|
@ -0,0 +1,35 @@
|
|||
package it.niedermann.owncloud.notes.shared.util.text;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.junit.Assert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class TextProcessorChainTest extends TestCase {
|
||||
|
||||
public void testApplyAllInOrder() {
|
||||
TextProcessorChain chain = new TextProcessorChain();
|
||||
chain.add(new SelfIdentifyingProcessor(1));
|
||||
chain.add(new SelfIdentifyingProcessor(2));
|
||||
|
||||
Assert.assertEquals("SelfIdentifyingProcessor 1\nSelfIdentifyingProcessor 2", chain.apply(""));
|
||||
}
|
||||
|
||||
class SelfIdentifyingProcessor extends TextProcessor {
|
||||
private int id;
|
||||
|
||||
public SelfIdentifyingProcessor(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String process(String s) {
|
||||
List<String> parts = new ArrayList<>(Arrays.asList(s.split("\n")));
|
||||
parts.add(String.format("%s %d", getClass().getSimpleName(), id));
|
||||
return String.join("\n", parts.toArray(new String[]{})).trim();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package it.niedermann.owncloud.notes.shared.util.text;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.junit.Assert;
|
||||
|
||||
public class WwwLinksProcessorTest extends TestCase {
|
||||
|
||||
public void testEmptyString() {
|
||||
TextProcessor sut = new WwwLinksProcessor();
|
||||
|
||||
String markdown = "";
|
||||
String result = sut.process(markdown);
|
||||
Assert.assertEquals("", result);
|
||||
}
|
||||
|
||||
public void testDoNotChangeOtherMarkdownElements() {
|
||||
TextProcessor sut = new WwwLinksProcessor();
|
||||
|
||||
//language=md
|
||||
String markdown = "\n" +
|
||||
"# heading \n" +
|
||||
" \n" +
|
||||
"This is a _markdown_ document. \n" +
|
||||
" \n" +
|
||||
"But\n" +
|
||||
" - there \n" +
|
||||
" - are \n" +
|
||||
" - no \n" +
|
||||
"\n" +
|
||||
"link elements.\n" +
|
||||
"\n" +
|
||||
"----\n" +
|
||||
"**Everything** else could be in here.\n" +
|
||||
"\n";
|
||||
|
||||
Assert.assertEquals(markdown, sut.process(markdown));
|
||||
}
|
||||
|
||||
@SuppressWarnings("MarkdownUnresolvedFileReference")
|
||||
public void testDoNotReplaceNormalLinks() {
|
||||
TextProcessor sut = new WwwLinksProcessor();
|
||||
|
||||
//language=md
|
||||
String markdown = "[normal link](https://example.com) and another [www link](www.example.com) and one more [normal link](https://www.example.com)";
|
||||
String result = sut.process(markdown);
|
||||
Assert.assertEquals("[normal link](https://example.com) and another [www link](http://www.example.com) and one more [normal link](https://www.example.com)", result);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue