Working with realtive urls
This commit is contained in:
parent
9fbf5ff1b1
commit
250dd7677d
@ -97,6 +97,8 @@ Lorem ipsum `dolor` sit amet
|
||||
Lorem ipsum dolor `sit` amet
|
||||
Lorem ipsum dolor sit `amet`
|
||||
|
||||
`Lorem ipsum dolor sit amet`
|
||||
|
||||
### Code block
|
||||
// todo syntax higlight
|
||||
```
|
||||
|
@ -55,8 +55,8 @@ class AppModule {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
UrlProvider urlProvider() {
|
||||
return new UrlProviderImpl();
|
||||
UriProcessor uriProcessor() {
|
||||
return new UriProcessorImpl();
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
12
app/src/main/java/ru/noties/markwon/CollectionUtils.java
Normal file
12
app/src/main/java/ru/noties/markwon/CollectionUtils.java
Normal file
@ -0,0 +1,12 @@
|
||||
package ru.noties.markwon;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public abstract class CollectionUtils {
|
||||
|
||||
public static boolean isEmpty(Collection<?> collection) {
|
||||
return collection == null || collection.size() == 0;
|
||||
}
|
||||
|
||||
private CollectionUtils() {}
|
||||
}
|
@ -27,6 +27,9 @@ public class MainActivity extends Activity {
|
||||
@Inject
|
||||
Themes themes;
|
||||
|
||||
@Inject
|
||||
UriProcessor uriProcessor;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@ -38,9 +41,13 @@ public class MainActivity extends Activity {
|
||||
themes.apply(this);
|
||||
|
||||
// how can we obtain SpannableConfiguration after theme was applied?
|
||||
// as we inject `themes` we won't be able to inject configuration, as it requires theme set
|
||||
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
// we process additionally github urls, as if url has in path `blob`, we won't receive
|
||||
// desired file, but instead rendered html
|
||||
checkUri();
|
||||
|
||||
final AppBarItem.Renderer appBarRenderer
|
||||
= new AppBarItem.Renderer(findViewById(R.id.app_bar), new View.OnClickListener() {
|
||||
@ -59,7 +66,7 @@ public class MainActivity extends Activity {
|
||||
markdownLoader.load(uri(), new MarkdownLoader.OnMarkdownTextLoaded() {
|
||||
@Override
|
||||
public void apply(String text) {
|
||||
markdownRenderer.render(MainActivity.this, text, new MarkdownRenderer.MarkdownReadyListener() {
|
||||
markdownRenderer.render(MainActivity.this, uri(), text, new MarkdownRenderer.MarkdownReadyListener() {
|
||||
@Override
|
||||
public void onMarkdownReady(CharSequence markdown) {
|
||||
Markwon.setText(textView, markdown);
|
||||
@ -80,8 +87,6 @@ public class MainActivity extends Activity {
|
||||
|
||||
final Uri uri = uri();
|
||||
|
||||
Debug.i(uri);
|
||||
|
||||
if (uri != null) {
|
||||
title = uri.getLastPathSegment();
|
||||
subtitle = uri.toString();
|
||||
@ -93,6 +98,13 @@ public class MainActivity extends Activity {
|
||||
return new AppBarItem.State(title, subtitle);
|
||||
}
|
||||
|
||||
private void checkUri() {
|
||||
final Uri uri = uri();
|
||||
if (uri != null) {
|
||||
getIntent().setData(uriProcessor.process(uri));
|
||||
}
|
||||
}
|
||||
|
||||
private Uri uri() {
|
||||
final Intent intent = getIntent();
|
||||
return intent != null
|
||||
|
@ -45,9 +45,6 @@ public class MarkdownLoader {
|
||||
@Inject
|
||||
OkHttpClient client;
|
||||
|
||||
@Inject
|
||||
UrlProvider urlProvider;
|
||||
|
||||
private Future<?> task;
|
||||
|
||||
@Inject
|
||||
@ -130,10 +127,8 @@ public class MarkdownLoader {
|
||||
|
||||
private String loadExternalUrl(@NonNull Uri uri) {
|
||||
|
||||
final String url = urlProvider.provide(uri);
|
||||
|
||||
final Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.url(uri.toString())
|
||||
.build();
|
||||
|
||||
Response response = null;
|
||||
|
@ -1,14 +1,23 @@
|
||||
package ru.noties.markwon;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension;
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.parser.Parser;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import ru.noties.markwon.renderer.SpannableRenderer;
|
||||
|
||||
@ActivityScope
|
||||
public class MarkdownRenderer {
|
||||
|
||||
@ -31,15 +40,37 @@ public class MarkdownRenderer {
|
||||
MarkdownRenderer() {
|
||||
}
|
||||
|
||||
public void render(@NonNull final Context context, @NonNull final String markdown, @NonNull final MarkdownReadyListener listener) {
|
||||
public void render(
|
||||
@NonNull final Context context,
|
||||
@Nullable final Uri uri,
|
||||
@NonNull final String markdown,
|
||||
@NonNull final MarkdownReadyListener listener) {
|
||||
cancel();
|
||||
task = service.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
final UrlProcessor urlProcessor;
|
||||
if (uri == null) {
|
||||
urlProcessor = null;
|
||||
} else {
|
||||
urlProcessor = new UrlProcessorRelativeToAbsolute(uri.toString());
|
||||
}
|
||||
|
||||
final SpannableConfiguration configuration = SpannableConfiguration.builder(context)
|
||||
.asyncDrawableLoader(loader)
|
||||
.urlProcessor(urlProcessor)
|
||||
.build();
|
||||
final CharSequence text = Markwon.markdown(configuration, markdown);
|
||||
|
||||
final Parser parser = Parser.builder()
|
||||
.extensions(Collections.singleton(StrikethroughExtension.create()))
|
||||
.build();
|
||||
|
||||
final Node node = parser.parse(markdown);
|
||||
final SpannableRenderer renderer = new SpannableRenderer();
|
||||
final CharSequence text = renderer.render(configuration, node);
|
||||
|
||||
// final CharSequence text = Markwon.markdown(configuration, markdown);
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
9
app/src/main/java/ru/noties/markwon/UriProcessor.java
Normal file
9
app/src/main/java/ru/noties/markwon/UriProcessor.java
Normal file
@ -0,0 +1,9 @@
|
||||
package ru.noties.markwon;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public interface UriProcessor {
|
||||
Uri process(@NonNull Uri uri);
|
||||
}
|
@ -5,25 +5,33 @@ import android.support.annotation.NonNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
class UrlProviderImpl implements UrlProvider {
|
||||
class UriProcessorImpl implements UriProcessor {
|
||||
|
||||
private static final String GITHUB = "github.com";
|
||||
|
||||
@Override
|
||||
public String provide(@NonNull Uri uri) {
|
||||
public Uri process(@NonNull final Uri uri) {
|
||||
|
||||
// hm... github, even having a README.md in path will return rendered HTML
|
||||
|
||||
final Uri out;
|
||||
|
||||
if (GITHUB.equals(uri.getAuthority())) {
|
||||
|
||||
final List<String> segments = uri.getPathSegments();
|
||||
if (segments != null
|
||||
&& segments.contains("blob")) {
|
||||
final int size = segments != null
|
||||
? segments.size()
|
||||
: 0;
|
||||
|
||||
if (size > 0) {
|
||||
|
||||
// we need to modify the final uri
|
||||
final Uri.Builder builder = new Uri.Builder()
|
||||
.scheme(uri.getScheme())
|
||||
.authority(uri.getAuthority())
|
||||
.fragment(uri.getFragment())
|
||||
.query(uri.getQuery());
|
||||
|
||||
for (String segment: segments) {
|
||||
final String part;
|
||||
if ("blob".equals(segment)) {
|
||||
@ -33,10 +41,14 @@ class UrlProviderImpl implements UrlProvider {
|
||||
}
|
||||
builder.appendPath(part);
|
||||
}
|
||||
uri = builder.build();
|
||||
out = builder.build();
|
||||
} else {
|
||||
out = uri;
|
||||
}
|
||||
} else {
|
||||
out = uri;
|
||||
}
|
||||
|
||||
return uri.toString();
|
||||
return out;
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package ru.noties.markwon;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
public interface UrlProvider {
|
||||
String provide(@NonNull Uri uri);
|
||||
}
|
@ -24,6 +24,7 @@ public class SpannableConfiguration {
|
||||
private final AsyncDrawable.Loader asyncDrawableLoader;
|
||||
private final SyntaxHighlight syntaxHighlight;
|
||||
private final LinkSpan.Resolver linkResolver;
|
||||
private final UrlProcessor urlProcessor;
|
||||
private final SpannableHtmlParser htmlParser;
|
||||
|
||||
private SpannableConfiguration(Builder builder) {
|
||||
@ -31,6 +32,7 @@ public class SpannableConfiguration {
|
||||
this.asyncDrawableLoader = builder.asyncDrawableLoader;
|
||||
this.syntaxHighlight = builder.syntaxHighlight;
|
||||
this.linkResolver = builder.linkResolver;
|
||||
this.urlProcessor = builder.urlProcessor;
|
||||
this.htmlParser = builder.htmlParser;
|
||||
}
|
||||
|
||||
@ -50,6 +52,10 @@ public class SpannableConfiguration {
|
||||
return linkResolver;
|
||||
}
|
||||
|
||||
public UrlProcessor urlProcessor() {
|
||||
return urlProcessor;
|
||||
}
|
||||
|
||||
public SpannableHtmlParser htmlParser() {
|
||||
return htmlParser;
|
||||
}
|
||||
@ -61,6 +67,7 @@ public class SpannableConfiguration {
|
||||
private AsyncDrawable.Loader asyncDrawableLoader;
|
||||
private SyntaxHighlight syntaxHighlight;
|
||||
private LinkSpan.Resolver linkResolver;
|
||||
private UrlProcessor urlProcessor;
|
||||
private SpannableHtmlParser htmlParser;
|
||||
|
||||
public Builder(Context context) {
|
||||
@ -87,6 +94,11 @@ public class SpannableConfiguration {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder urlProcessor(UrlProcessor urlProcessor) {
|
||||
this.urlProcessor = urlProcessor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder htmlParser(SpannableHtmlParser htmlParser) {
|
||||
this.htmlParser = htmlParser;
|
||||
return this;
|
||||
@ -105,8 +117,11 @@ public class SpannableConfiguration {
|
||||
if (linkResolver == null) {
|
||||
linkResolver = new LinkResolverDef();
|
||||
}
|
||||
if (urlProcessor == null) {
|
||||
urlProcessor = new UrlProcessorNoOp();
|
||||
}
|
||||
if (htmlParser == null) {
|
||||
htmlParser = SpannableHtmlParser.create(theme, asyncDrawableLoader);
|
||||
htmlParser = SpannableHtmlParser.create(theme, asyncDrawableLoader, urlProcessor);
|
||||
}
|
||||
return new SpannableConfiguration(this);
|
||||
}
|
||||
|
@ -0,0 +1,8 @@
|
||||
package ru.noties.markwon;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
public interface UrlProcessor {
|
||||
@NonNull
|
||||
String process(@NonNull String destination);
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package ru.noties.markwon;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
public class UrlProcessorNoOp implements UrlProcessor {
|
||||
@NonNull
|
||||
@Override
|
||||
public String process(@NonNull String destination) {
|
||||
return destination;
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package ru.noties.markwon;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
public class UrlProcessorRelativeToAbsolute implements UrlProcessor {
|
||||
|
||||
private final URL base;
|
||||
|
||||
public UrlProcessorRelativeToAbsolute(@NonNull String base) {
|
||||
this.base = obtain(base);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String process(@NonNull String destination) {
|
||||
|
||||
String out = destination;
|
||||
|
||||
if (base != null) {
|
||||
try {
|
||||
final URL u = new URL(base, destination);
|
||||
out = u.toString();
|
||||
} catch (MalformedURLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static URL obtain(String base) {
|
||||
try {
|
||||
return new URL(base);
|
||||
} catch (MalformedURLException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -51,7 +51,7 @@ import ru.noties.markwon.spans.ThematicBreakSpan;
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
|
||||
private static final String HTML_CONTENT = "<%1$s>%2$s</%1$s>";
|
||||
private static final String HTML_CONTENT = "<%1$s>%2$s</%3$s>";
|
||||
|
||||
private final SpannableConfiguration configuration;
|
||||
private final SpannableStringBuilder builder;
|
||||
@ -253,7 +253,8 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
|
||||
@Override
|
||||
public void visit(SoftLineBreak softLineBreak) {
|
||||
newLine();
|
||||
// at first here was a new line, but here should be a space char
|
||||
builder.append(' ');
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -306,13 +307,14 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
|
||||
final Node parent = image.getParent();
|
||||
final boolean link = parent != null && parent instanceof Link;
|
||||
final String destination = configuration.urlProcessor().process(image.getDestination());
|
||||
|
||||
setSpan(
|
||||
length,
|
||||
new AsyncDrawableSpan(
|
||||
configuration.theme(),
|
||||
new AsyncDrawable(
|
||||
image.getDestination(),
|
||||
destination,
|
||||
configuration.asyncDrawableLoader()
|
||||
),
|
||||
AsyncDrawableSpan.ALIGN_BOTTOM,
|
||||
@ -351,7 +353,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
setSpan(item.start, span);
|
||||
} else {
|
||||
final String content = builder.subSequence(start, builder.length()).toString();
|
||||
final String html = String.format(HTML_CONTENT, item.tag, content);
|
||||
final String html = String.format(HTML_CONTENT, item.tag, content, tag.name());
|
||||
final Object[] spans = htmlParser.htmlSpans(html);
|
||||
final int length = spans != null
|
||||
? spans.length
|
||||
@ -382,7 +384,8 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
public void visit(Link link) {
|
||||
final int length = builder.length();
|
||||
visitChildren(link);
|
||||
setSpan(length, new LinkSpan(configuration.theme(), link.getDestination(), configuration.linkResolver()));
|
||||
final String destination = configuration.urlProcessor().process(link.getDestination());
|
||||
setSpan(length, new LinkSpan(configuration.theme(), destination, configuration.linkResolver()));
|
||||
}
|
||||
|
||||
private void setSpan(int start, @NonNull Object span) {
|
||||
|
@ -2,20 +2,30 @@ package ru.noties.markwon.renderer.html;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.Html;
|
||||
|
||||
import ru.noties.markwon.UrlProcessor;
|
||||
import ru.noties.markwon.spans.AsyncDrawable;
|
||||
|
||||
class HtmlImageGetter implements Html.ImageGetter {
|
||||
|
||||
private final AsyncDrawable.Loader loader;
|
||||
private final UrlProcessor urlProcessor;
|
||||
|
||||
HtmlImageGetter(@NonNull AsyncDrawable.Loader loader) {
|
||||
HtmlImageGetter(@NonNull AsyncDrawable.Loader loader, @Nullable UrlProcessor urlProcessor) {
|
||||
this.loader = loader;
|
||||
this.urlProcessor = urlProcessor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable getDrawable(String source) {
|
||||
return new AsyncDrawable(source, loader);
|
||||
final String destination;
|
||||
if (urlProcessor == null) {
|
||||
destination = source;
|
||||
} else {
|
||||
destination = urlProcessor.process(source);
|
||||
}
|
||||
return new AsyncDrawable(destination, loader);
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import ru.noties.debug.Debug;
|
||||
import ru.noties.markwon.UrlProcessor;
|
||||
import ru.noties.markwon.spans.AsyncDrawable;
|
||||
import ru.noties.markwon.spans.SpannableTheme;
|
||||
|
||||
@ -22,8 +24,20 @@ public class SpannableHtmlParser {
|
||||
// we need to handle images independently (in order to parse alt, width, height, etc)
|
||||
|
||||
// creates default parser
|
||||
public static SpannableHtmlParser create(@NonNull SpannableTheme theme, @NonNull AsyncDrawable.Loader loader) {
|
||||
return builderWithDefaults(theme, loader)
|
||||
public static SpannableHtmlParser create(
|
||||
@NonNull SpannableTheme theme,
|
||||
@NonNull AsyncDrawable.Loader loader
|
||||
) {
|
||||
return builderWithDefaults(theme, loader, null)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static SpannableHtmlParser create(
|
||||
@NonNull SpannableTheme theme,
|
||||
@NonNull AsyncDrawable.Loader loader,
|
||||
@NonNull UrlProcessor urlProcessor
|
||||
) {
|
||||
return builderWithDefaults(theme, loader, urlProcessor)
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -33,7 +47,8 @@ public class SpannableHtmlParser {
|
||||
|
||||
public static Builder builderWithDefaults(
|
||||
@NonNull SpannableTheme theme,
|
||||
@Nullable AsyncDrawable.Loader asyncDrawableLoader
|
||||
@Nullable AsyncDrawable.Loader asyncDrawableLoader,
|
||||
@Nullable UrlProcessor urlProcessor
|
||||
) {
|
||||
|
||||
final BoldProvider boldProvider = new BoldProvider();
|
||||
@ -42,7 +57,7 @@ public class SpannableHtmlParser {
|
||||
|
||||
final HtmlParser parser;
|
||||
if (asyncDrawableLoader != null) {
|
||||
parser = DefaultHtmlParser.create(new HtmlImageGetter(asyncDrawableLoader), null);
|
||||
parser = DefaultHtmlParser.create(new HtmlImageGetter(asyncDrawableLoader, urlProcessor), null);
|
||||
} else {
|
||||
parser = DefaultHtmlParser.create(null, null);
|
||||
}
|
||||
@ -71,9 +86,12 @@ public class SpannableHtmlParser {
|
||||
|
||||
public interface HtmlParser {
|
||||
Object[] getSpans(@NonNull String html);
|
||||
|
||||
Spanned parse(@NonNull String html);
|
||||
}
|
||||
|
||||
private static final String LINK_START = "<a ";
|
||||
|
||||
private final Map<String, SpanProvider> customTags;
|
||||
private final Set<String> voidTags;
|
||||
private final HtmlParser parser;
|
||||
@ -97,7 +115,7 @@ public class SpannableHtmlParser {
|
||||
if (length < 3) {
|
||||
tag = null;
|
||||
} else {
|
||||
// okay, we will consider a tag a void one if it's in our void list tag or if it ends with `/>`
|
||||
// okay, we will consider a tag a void one if it's in our void list tag
|
||||
final boolean closing = '<' == html.charAt(0) && '/' == html.charAt(1);
|
||||
final boolean voidTag;
|
||||
if (closing) {
|
||||
@ -144,10 +162,14 @@ public class SpannableHtmlParser {
|
||||
@Nullable
|
||||
public Object[] htmlSpans(String html) {
|
||||
// todo, additional handling of: image & link
|
||||
Debug.i("html: %s", html);
|
||||
return parser.getSpans(html);
|
||||
}
|
||||
|
||||
// this is called when we encounter `void` tag
|
||||
// `img` is a void tag
|
||||
public Spanned html(String html) {
|
||||
Debug.i("html: %s", html);
|
||||
return parser.parse(html);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user