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