Create iframe plugin
This commit is contained in:
parent
c928750ff0
commit
50d331bc87
1
markwon-iframe-ext/.gitignore
vendored
Normal file
1
markwon-iframe-ext/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
48
markwon-iframe-ext/build.gradle
Normal file
48
markwon-iframe-ext/build.gradle
Normal file
@ -0,0 +1,48 @@
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 31
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 31
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles "consumer-rules.pro"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
api project(':markwon-core')
|
||||
|
||||
deps.with {
|
||||
// add a compileOnly dependency, so if this artifact is present
|
||||
// we will try to obtain a SpanFactory for a Strikethrough node and use
|
||||
// it to be consistent with markdown (please note that we do not use markwon plugin
|
||||
// for that in case if different implementation is used)
|
||||
compileOnly it['commonmark-strikethrough']
|
||||
|
||||
testImplementation it['ix-java']
|
||||
}
|
||||
|
||||
deps.test.with {
|
||||
testImplementation it['junit']
|
||||
testImplementation it['robolectric']
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package io.noties.markwon.iframe.ext;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
@Test
|
||||
public void useAppContext() {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
assertEquals("io.noties.markwon.iframe.ext.test", appContext.getPackageName());
|
||||
}
|
||||
}
|
5
markwon-iframe-ext/src/main/AndroidManifest.xml
Normal file
5
markwon-iframe-ext/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="io.noties.markwon.iframe.ext">
|
||||
|
||||
</manifest>
|
@ -0,0 +1,7 @@
|
||||
package io.noties.markwon.iframe.ext;
|
||||
|
||||
|
||||
import org.commonmark.node.CustomNode;
|
||||
|
||||
public class IFrameGroupNode extends CustomNode {
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package io.noties.markwon.iframe.ext;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.commonmark.node.CustomNode;
|
||||
|
||||
public class IFrameNode extends CustomNode {
|
||||
|
||||
public static final String DELIMITER_STRING = "![";
|
||||
private final String link;
|
||||
public IFrameNode(@NonNull String link) {
|
||||
this.link = link;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String link() {
|
||||
return link;
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package io.noties.markwon.iframe.ext;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.commonmark.parser.Parser;
|
||||
|
||||
import io.noties.markwon.AbstractMarkwonPlugin;
|
||||
import io.noties.markwon.MarkwonVisitor;
|
||||
|
||||
public class IFramePlugIn extends AbstractMarkwonPlugin {
|
||||
|
||||
@NonNull
|
||||
public static IFramePlugIn create() {
|
||||
return new IFramePlugIn();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureParser(@NonNull Parser.Builder builder) {
|
||||
builder.customDelimiterProcessor(IFrameProcessor.create());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
|
||||
builder.on(IFrameNode.class, new MarkwonVisitor.NodeVisitor<IFrameNode>() {
|
||||
@Override
|
||||
public void visit(@NonNull MarkwonVisitor visitor, @NonNull IFrameNode iFrameNode) {
|
||||
|
||||
final String link = iFrameNode.link();
|
||||
if (!TextUtils.isEmpty(link)) {
|
||||
visitor.builder().append(link);
|
||||
visitor.builder().append(' ');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String processMarkdown(@NonNull String markdown) {
|
||||
return IFrameProcessor.prepare(markdown);
|
||||
}
|
||||
}
|
@ -0,0 +1,166 @@
|
||||
package io.noties.markwon.iframe.ext;
|
||||
|
||||
import static io.noties.markwon.iframe.ext.IFrameUtils.getDesmosId;
|
||||
import static io.noties.markwon.iframe.ext.IFrameUtils.getVimeoVideoId;
|
||||
import static io.noties.markwon.iframe.ext.IFrameUtils.getYoutubeVideoId;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.node.Text;
|
||||
import org.commonmark.parser.delimiter.DelimiterProcessor;
|
||||
import org.commonmark.parser.delimiter.DelimiterRun;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class IFrameProcessor implements DelimiterProcessor {
|
||||
|
||||
private static final String TO_FIND_STRING = IFrameNode.DELIMITER_STRING;
|
||||
private static final String CLOSE_BRACKET = "]";
|
||||
public static final String REPLACE_STRING = "^";
|
||||
public static final char REPLACE_CHAR = '^';
|
||||
public static final String OPEN_STRING = "(";
|
||||
public static final String SEPARATOR_STRING = REPLACE_STRING + OPEN_STRING;
|
||||
public static final String CLOSE_STRING = ")";
|
||||
public static final char CLOSE_CHAR = ')';
|
||||
private static final Pattern PATTERN = Pattern.compile("!\\[[^\\]]*\\]\\([^)]+\\)");
|
||||
@NonNull
|
||||
public static IFrameProcessor create() {
|
||||
return new IFrameProcessor();
|
||||
}
|
||||
|
||||
|
||||
@NonNull
|
||||
public static String prepare(@NonNull String input) {
|
||||
final StringBuilder builder = new StringBuilder(input);
|
||||
if (builder.toString().toLowerCase().indexOf(TO_FIND_STRING) > -1) {
|
||||
prepare(builder, TO_FIND_STRING);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static void prepare(@NonNull StringBuilder builder, String finder) {
|
||||
|
||||
int start = builder.indexOf(finder);
|
||||
int end;
|
||||
|
||||
while (start > -1) {
|
||||
|
||||
end = inLineDefinitionEnd(start + finder.length(), builder);
|
||||
if (iconDefinitionValid(builder.subSequence(start, end))) {
|
||||
int startEndBracket = builder.indexOf(CLOSE_BRACKET, start + 2);
|
||||
if (checkingURLValidate(builder.substring(startEndBracket + 2, end - 1))) {
|
||||
builder.replace(start, start + 2, REPLACE_STRING);
|
||||
builder.replace(startEndBracket - 1, startEndBracket, REPLACE_STRING);
|
||||
}
|
||||
}
|
||||
// move to next
|
||||
start = builder.indexOf(finder, end);
|
||||
}
|
||||
}
|
||||
|
||||
private static int inLineDefinitionEnd(int startIndex, @NonNull StringBuilder builder) {
|
||||
|
||||
// all spaces, new lines, non-words or digits,
|
||||
|
||||
char c;
|
||||
|
||||
int end = -1;
|
||||
for (int i = startIndex; i < builder.length(); i++) {
|
||||
c = builder.charAt(i);
|
||||
if (c == ')') {
|
||||
end = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (end == -1) {
|
||||
end = builder.length();
|
||||
}
|
||||
|
||||
return end;
|
||||
}
|
||||
|
||||
private static boolean iconDefinitionValid(@NonNull CharSequence cs) {
|
||||
final Matcher matcher = PATTERN.matcher(cs);
|
||||
return matcher.matches();
|
||||
}
|
||||
|
||||
private static boolean checkingURLValidate(@NonNull String cs) {
|
||||
if (getYoutubeVideoId(cs) != null && !getYoutubeVideoId(cs).isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
if (getVimeoVideoId(cs) != null && !getVimeoVideoId(cs).isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
if (getDesmosId(cs) != null && !getDesmosId(cs).isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getOpeningCharacter() {
|
||||
return REPLACE_CHAR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getClosingCharacter() {
|
||||
return CLOSE_CHAR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinLength() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) {
|
||||
return opener.length() >= 1 && closer.length() >= 1 ? 1 : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(Text opener, Text closer, int delimiterUse) {
|
||||
|
||||
final IFrameGroupNode iFrameGroupNode = new IFrameGroupNode();
|
||||
|
||||
final Node next = opener.getNext();
|
||||
|
||||
boolean handled = false;
|
||||
|
||||
// process only if we have exactly one Text node
|
||||
|
||||
|
||||
final String text = ((Text) next).getLiteral();
|
||||
|
||||
if (!TextUtils.isEmpty(text)) {
|
||||
// attempt to match
|
||||
int start_link_index = text.indexOf(OPEN_STRING);
|
||||
if (start_link_index >= 0) {
|
||||
String link = text.substring(start_link_index + OPEN_STRING.length());
|
||||
IFrameNode iFrameNode = new IFrameNode(link);
|
||||
iFrameGroupNode.appendChild(iFrameNode);
|
||||
next.unlink();
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
iFrameGroupNode.appendChild(new Text(REPLACE_STRING));
|
||||
Node node;
|
||||
for (Node tmp = opener.getNext(); tmp != null && tmp != closer; tmp = node) {
|
||||
node = tmp.getNext();
|
||||
// append a child anyway
|
||||
iFrameGroupNode.appendChild(tmp);
|
||||
}
|
||||
|
||||
iFrameGroupNode.appendChild(new Text(CLOSE_STRING));
|
||||
}
|
||||
opener.setLiteral("");
|
||||
closer.setLiteral("");
|
||||
opener.insertBefore(iFrameGroupNode);
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package io.noties.markwon.iframe.ext;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class IFrameUtils {
|
||||
private final static String youtube_expression = "(?<=watch\\?v=|/videos/|embed\\/|youtu.be\\/|\\/v\\/|\\/e\\/|watch\\?v%3D|watch\\?feature=player_embedded&v=|%2Fvideos%2F|embed%\u200C\u200B2F|youtu.be%2F|%2Fv%2F)[^#\\&\\?\\n]*";
|
||||
public static String getYoutubeVideoId(String videoUrl) {
|
||||
if (videoUrl == null || videoUrl.trim().length() <= 0){
|
||||
return null;
|
||||
}
|
||||
Pattern pattern = Pattern.compile(youtube_expression);
|
||||
Matcher matcher = pattern.matcher(videoUrl);
|
||||
try {
|
||||
if (matcher.find())
|
||||
return matcher.group();
|
||||
} catch (ArrayIndexOutOfBoundsException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private final static String vimeo_expression = "https?:\\/\\/(?:www\\.)?vimeo.com\\/(?:channels\\/(?:\\w+\\/)?|groups\\/([^\\/]*)\\/videos\\/|album\\/(\\d+)\\/video\\/|)(\\d+)(?:$|\\/|\\?)";
|
||||
public static String getVimeoVideoId(String videoUrl) {
|
||||
if (videoUrl == null || videoUrl.trim().length() <= 0){
|
||||
return null;
|
||||
}
|
||||
Pattern pattern = Pattern.compile(vimeo_expression);
|
||||
Matcher matcher = pattern.matcher(videoUrl);
|
||||
try {
|
||||
if (matcher.find())
|
||||
return matcher.group(3);
|
||||
} catch (ArrayIndexOutOfBoundsException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private final static String desmos_expression = "https?:\\/\\/(www\\.)?desmos.com\\/calculator\\/(\\w+)($|\\/)";
|
||||
public static String getDesmosId(String desmosURL) {
|
||||
if (desmosURL == null || desmosURL.trim().length() <= 0){
|
||||
return null;
|
||||
}
|
||||
Pattern pattern = Pattern.compile(desmos_expression);
|
||||
Matcher matcher = pattern.matcher(desmosURL);
|
||||
try {
|
||||
if (matcher.find())
|
||||
return matcher.group(2);
|
||||
} catch (ArrayIndexOutOfBoundsException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package io.noties.markwon.iframe.ext;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
@Test
|
||||
public void addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user