Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 77677768 authored by Ferdinand Mütsch's avatar Ferdinand Mütsch Committed by Niedermann IT-Dienstleistungen
Browse files

support www links without protocol in preview mode (resolve #949)

parent 2d1e4e5b
Loading
Loading
Loading
Loading
+17 −4
Original line number Diff line number Diff line
@@ -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;
    }
}
+6 −46
Original line number Diff line number Diff line
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, ""));
    }
}
+57 −0
Original line number Diff line number Diff line
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));
    }
}
+10 −0
Original line number Diff line number Diff line
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);
}
 No newline at end of file
+12 −0
Original line number Diff line number Diff line
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;
    }
}
Loading