├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ └── dummy_data.json │ ├── java │ └── work │ │ └── upstarts │ │ ├── MainActivity.kt │ │ └── SpacesItemDecoration.kt │ └── res │ ├── layout │ └── activity_main.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle.kts ├── editorjskit ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── work │ │ └── upstarts │ │ └── editorjskit │ │ ├── EJKit.kt │ │ ├── environment │ │ └── Extensions.kt │ │ ├── models │ │ ├── EJAbstractCustomBlock.kt │ │ ├── EJBlock.kt │ │ ├── EJBlockType.kt │ │ ├── HeadingLevel.kt │ │ ├── Margins.kt │ │ └── data │ │ │ ├── EJData.kt │ │ │ ├── EJDelimiterData.kt │ │ │ ├── EJHeaderData.kt │ │ │ ├── EJImageData.kt │ │ │ ├── EJListData.kt │ │ │ ├── EJParagraphData.kt │ │ │ ├── EJRawHtmlData.kt │ │ │ └── EJTableData.kt │ │ └── ui │ │ ├── EJAdapter.kt │ │ ├── adapterdelegates │ │ ├── DividerAdapterDelegate.kt │ │ ├── HeaderAdapterDelegate.kt │ │ ├── ImageAdapterDelegate.kt │ │ ├── ListAdapterDelegate.kt │ │ ├── ParagraphAdapterDelegate.kt │ │ ├── RawHtmlAdapterDelegate.kt │ │ └── TableAdapterDelegate.kt │ │ ├── theme │ │ └── EJStyle.kt │ │ └── views │ │ └── HeaderTextView.kt │ └── res │ ├── drawable │ ├── image_background.xml │ ├── list_circle.xml │ └── table_content_cell_bg.xml │ ├── layout │ ├── item_column.xml │ ├── item_divider.xml │ ├── item_header.xml │ ├── item_image.xml │ ├── item_list.xml │ ├── item_list_row.xml │ ├── item_paragraph.xml │ └── item_table.xml │ └── values │ ├── color.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── gradle.properties ├── gradle ├── libs.versions.toml ├── publish.gradle ├── versions.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── gsonparser ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── work │ └── upstarts │ └── gsonparser │ └── EJDeserializer.kt ├── jitpack.yml ├── moshiparser ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── work │ │ └── upstarts │ │ └── moshiparser │ │ ├── EJAbstractBlockTypeAdapter.kt │ │ └── EJBlockAdapter.kt │ └── test │ └── java │ └── work │ └── upstarts │ └── moshiparser │ └── EJBlockAdapterTest.kt ├── readme.md ├── scripts ├── publish-module.gradle └── publish-root.gradle └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Change Log 2 | ========== 3 | 4 | ## Version 2.0.0 5 | 6 | _2021-08-04_ 7 | 8 | * Breaking changes: 9 | - EJBLock now is interface 10 | * Drop kotlin-android-extensions 11 | * Adapter for moshi 12 | * Update gradle and dependencies 13 | 14 | ## Version 1.0.3 15 | 16 | _2020-08-26_ 17 | 18 | * ability to adjust bullet size in lists (both width and height), default is 4dp for both 19 | 20 | ## Version 1.0.1 21 | 22 | _2020-06-24_ 23 | 24 | * ability to adjust margins added for all view types exept tables 25 | 26 | ## Version 0.0.9 27 | 28 | _2020-05-19_ 29 | 30 | * Fix clicking on hyperlinks 31 | 32 | ## Version 0.0.8 33 | 34 | _2020-05-04_ 35 | 36 | * Add basic proguard rules 37 | 38 | * Update all dependencies. Add file versions.gradle and declare all versions there 39 | 40 | * Move GSON adapter as separate dependency. If you use other library for parsing json, you need to write adapter 41 | yourself. 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Upstarts team 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("kotlin-android") 4 | } 5 | 6 | android { 7 | compileSdk = libs.versions.compileSdk.get().toInt() 8 | defaultConfig { 9 | applicationId = "work.upstarts" 10 | minSdk = libs.versions.minSdk.get().toInt() 11 | targetSdk = libs.versions.targetSdk.get().toInt() 12 | versionCode = 1 13 | versionName = "1.0" 14 | resourceConfigurations.addAll(setOf("en", "ru")) 15 | } 16 | buildTypes { 17 | release { 18 | isShrinkResources = false 19 | isMinifyEnabled = false 20 | proguardFiles( 21 | getDefaultProguardFile("proguard-android-optimize.txt"), 22 | "proguard-rules.pro" 23 | ) 24 | } 25 | } 26 | namespace = "work.upstarts" 27 | compileOptions { 28 | sourceCompatibility = JavaVersion.VERSION_17 29 | targetCompatibility = JavaVersion.VERSION_17 30 | } 31 | kotlinOptions { 32 | jvmTarget = "17" 33 | } 34 | } 35 | 36 | dependencies { 37 | implementation(libs.androidx.core.ktx) 38 | implementation(libs.androidx.appcompat) 39 | implementation(libs.androidx.constraintlayout) 40 | implementation(libs.recyclerview) 41 | implementation(project(":editorjskit")) 42 | implementation(project(":gsonparser")) 43 | } 44 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -keep class work.upstarts.EJResponse { *; } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/assets/dummy_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "time": 1560338876987, 3 | "blocks": [ 4 | { 5 | "type": "header", 6 | "data": { 7 | "text": "Editor.js Kit Android", 8 | "level": 1 9 | } 10 | }, 11 | { 12 | "type" : "paragraph", 13 | "data" : { 14 | "text" : "Render native UI with formatted JSON data." 15 | } 16 | }, 17 | { 18 | "type": "image", 19 | "data": { 20 | "file": { 21 | "url": "https://static.upstarts.work/ejkit/logo-light.png" 22 | }, 23 | "caption": "Brought to you by Upstarts team", 24 | "withBorder": true, 25 | "stretched": false, 26 | "withBackground": false 27 | } 28 | }, 29 | { 30 | "type" : "list", 31 | "data" : { 32 | "style" : "ordered", 33 | "items" : [ 34 | "Editor.JS is a block-styled editor", 35 | "The Kit renders clean JSON data natively", 36 | "Designed to be extendable and pluggable" 37 | ] 38 | } 39 | }, 40 | { 41 | "type" : "linkTool", 42 | "data" : { 43 | "link" : "https://upstarts.work", 44 | "meta" : { 45 | "title" : "Upstarts Team", 46 | "site_name" : "Upstarts", 47 | "description" : "Mobile development team. We develop mobile apps for iOS and Android. We research, prototype, sketch, and code apps & backend business logic.", 48 | "image" : { 49 | "url" : "https://static.upstarts.work/img/logo-blue-300px.png" 50 | } 51 | } 52 | } 53 | }, 54 | { 55 | "type": "delimiter", 56 | "data" : {} 57 | }, 58 | { 59 | "type" : "paragraph", 60 | "data" : { 61 | "text" : "Workspace in classic editors is made of a single contenteditable element, used to create different HTML markups. Editor.js workspace consists of separate Blocks: paragraphs, headings, images, lists, quotes, etc. Each of them is an independent contenteditable element (or more complex structure) provided by Plugin and united by Editor's Core." 62 | } 63 | }, 64 | { 65 | "type" : "paragraph", 66 | "data" : { 67 | "text" : "There are dozens of ready-to-use Blocks and the simple API for creation any Block you need. For example, you can implement Blocks for Tweets, Instagram posts, surveys and polls, CTA-buttons and even games." 68 | } 69 | }, 70 | { 71 | "type" : "paragraph", 72 | "data" : { 73 | "text" : "Given data can be used as you want: render with HTML for Web clients, render natively for mobile apps, create markup for Facebook Instant Articles or Google AMP, generate an audio version and so on." 74 | } 75 | }, 76 | { 77 | "type" : "raw", 78 | "data" : { 79 | "html": "
Any raw HTML code
" 80 | } 81 | } 82 | ], 83 | "version" : "2.13.2" 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/java/work/upstarts/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts 2 | 3 | import android.content.res.AssetManager 4 | import android.graphics.Color 5 | import android.os.Bundle 6 | import androidx.appcompat.app.AppCompatActivity 7 | import androidx.core.content.ContextCompat 8 | import androidx.recyclerview.widget.LinearLayoutManager 9 | import androidx.recyclerview.widget.RecyclerView 10 | import com.google.gson.GsonBuilder 11 | import com.google.gson.reflect.TypeToken 12 | import work.upstarts.editorjskit.environment.dp 13 | import work.upstarts.editorjskit.models.EJBlock 14 | import work.upstarts.editorjskit.models.HeadingLevel 15 | import work.upstarts.editorjskit.ui.EditorJsAdapter 16 | import work.upstarts.editorjskit.ui.theme.EJStyle 17 | import work.upstarts.gsonparser.EJDeserializer 18 | import work.upstarts.editorjskit.R as EditorJsR 19 | 20 | const val DATA_JSON_PATH = "dummy_data.json" 21 | 22 | data class EJResponse(val blocks: List) 23 | 24 | class MainActivity : AppCompatActivity() { 25 | 26 | private val rvAdapter: EditorJsAdapter by lazy { 27 | EditorJsAdapter( 28 | EJStyle.builderWithDefaults(applicationContext) 29 | .linkColor(ContextCompat.getColor(this, EditorJsR.color.link_color)) 30 | .linkColor(ContextCompat.getColor(this, EditorJsR.color.link_color)) 31 | .headingMargin( 32 | ZERO_MARGIN, 33 | ZERO_MARGIN, 34 | ZERO_MARGIN, 35 | ZERO_MARGIN, 36 | HeadingLevel.h1 37 | ) 38 | .headingMargin( 39 | STANDARD_MARGIN, 40 | ZERO_MARGIN, 41 | ZERO_MARGIN, 42 | ZERO_MARGIN, 43 | HeadingLevel.h2 44 | ) 45 | .linkColor(ContextCompat.getColor(this, EditorJsR.color.link_color)) 46 | .dividerBreakHeight(DIVIDER_HEIGHT.dp) 47 | .dividerMargin(0.dp, 4.dp, 0.dp, 4.dp) 48 | .dividerBreakColor(Color.parseColor("#32000000")) 49 | .build() 50 | ) 51 | } 52 | 53 | override fun onCreate(savedInstanceState: Bundle?) { 54 | super.onCreate(savedInstanceState) 55 | setContentView(R.layout.activity_main) 56 | 57 | val recyclerView: RecyclerView = findViewById(R.id.recyclerView) 58 | with(recyclerView) { 59 | layoutManager = LinearLayoutManager(context) 60 | adapter = rvAdapter 61 | } 62 | 63 | val blocksType = object : TypeToken>() {}.type 64 | 65 | val gson = GsonBuilder() 66 | .registerTypeAdapter(blocksType, EJDeserializer()) 67 | .create() 68 | 69 | val dummyData = readFileFromAssets(DATA_JSON_PATH, assets) 70 | val ejResponse = gson.fromJson(dummyData, EJResponse::class.java) 71 | 72 | rvAdapter.items = ejResponse.blocks 73 | 74 | } 75 | } 76 | 77 | fun readFileFromAssets(fname: String, assetsManager: AssetManager) = 78 | assetsManager.open(fname).readBytes().toString(Charsets.UTF_8) 79 | 80 | const val ZERO_MARGIN = 0 81 | const val STANDARD_MARGIN = 16 82 | const val DIVIDER_HEIGHT = 1 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /app/src/main/java/work/upstarts/SpacesItemDecoration.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts 2 | 3 | import android.graphics.Rect 4 | import android.view.View 5 | import androidx.recyclerview.widget.RecyclerView 6 | 7 | 8 | class SpacesItemDecoration(private val space: Int) : RecyclerView.ItemDecoration() { 9 | 10 | override fun getItemOffsets( 11 | outRect: Rect, view: View, 12 | parent: RecyclerView, state: RecyclerView.State 13 | ) { 14 | outRect.left = space 15 | outRect.right = space 16 | outRect.bottom = space 17 | 18 | // Add top margin only for the first item to avoid double space between items 19 | if (parent.getChildLayoutPosition(view) == 0) { 20 | outRect.top = space 21 | } else { 22 | outRect.top = 0 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upstarts/editor.js-kit-android/469242ccf57936494a8c460d41bf2260c9062de6/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upstarts/editor.js-kit-android/469242ccf57936494a8c460d41bf2260c9062de6/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upstarts/editor.js-kit-android/469242ccf57936494a8c460d41bf2260c9062de6/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upstarts/editor.js-kit-android/469242ccf57936494a8c460d41bf2260c9062de6/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upstarts/editor.js-kit-android/469242ccf57936494a8c460d41bf2260c9062de6/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upstarts/editor.js-kit-android/469242ccf57936494a8c460d41bf2260c9062de6/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upstarts/editor.js-kit-android/469242ccf57936494a8c460d41bf2260c9062de6/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upstarts/editor.js-kit-android/469242ccf57936494a8c460d41bf2260c9062de6/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upstarts/editor.js-kit-android/469242ccf57936494a8c460d41bf2260c9062de6/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upstarts/editor.js-kit-android/469242ccf57936494a8c460d41bf2260c9062de6/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 30dp 4 | 16sp 5 | 4dp 6 | 2dp 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | EditorJSKitSample 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 14 | 15 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | plugins { 3 | alias(libs.plugins.android.application) apply false 4 | alias(libs.plugins.jetbrains.kotlin.android) apply false 5 | } 6 | 7 | allprojects { 8 | plugins.withId("org.gradle.maven-publish") { 9 | group = "io.github.upstarts" 10 | version = libs.versions.publishVersion.get() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /editorjskit/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /editorjskit/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | id("org.jetbrains.kotlin.android") 4 | id("maven-publish") 5 | } 6 | 7 | android { 8 | namespace = "work.upstarts.editorjskit" 9 | compileSdk = libs.versions.compileSdk.get().toInt() 10 | defaultConfig { 11 | minSdk = libs.versions.minSdk.get().toInt() 12 | consumerProguardFiles("consumer-rules.pro") 13 | } 14 | buildTypes { 15 | release { 16 | isMinifyEnabled = false 17 | } 18 | } 19 | lint { 20 | warning.add("InvalidPackage") 21 | } 22 | compileOptions { 23 | sourceCompatibility = JavaVersion.VERSION_17 24 | targetCompatibility = JavaVersion.VERSION_17 25 | } 26 | kotlinOptions { 27 | jvmTarget = "17" 28 | } 29 | publishing { 30 | multipleVariants { 31 | allVariants() 32 | withJavadocJar() 33 | withSourcesJar() 34 | } 35 | } 36 | } 37 | 38 | dependencies { 39 | api(libs.adapter.delegates) 40 | implementation(libs.androidx.appcompat) 41 | implementation(libs.androidx.core.ktx) 42 | implementation(libs.androidx.constraintlayout) 43 | implementation(libs.glide) 44 | } 45 | 46 | afterEvaluate { 47 | publishing { 48 | publications { 49 | create("mavenRelease") { 50 | groupId = "io.github.upstarts" 51 | artifactId = "ejkit" 52 | version = libs.versions.publishVersion.get() 53 | 54 | from(components["release"]) 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /editorjskit/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -keep class work.upstarts.editorjskit.models.** { *; } -------------------------------------------------------------------------------- /editorjskit/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /editorjskit/src/main/java/work/upstarts/editorjskit/EJKit.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts.editorjskit 2 | 3 | import work.upstarts.editorjskit.models.EJAbstractCustomBlock 4 | import work.upstarts.editorjskit.ui.theme.EJStyle 5 | 6 | object EJKit { 7 | var ejStyle: EJStyle? = null 8 | 9 | private val customBlocks: MutableList = mutableListOf() 10 | 11 | fun register(customBlock: EJAbstractCustomBlock) { 12 | customBlocks.add(customBlock) 13 | } 14 | 15 | fun getCustomBlocks(): List { 16 | return customBlocks 17 | } 18 | } -------------------------------------------------------------------------------- /editorjskit/src/main/java/work/upstarts/editorjskit/environment/Extensions.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts.editorjskit.environment 2 | 3 | import android.content.Context 4 | import android.content.res.Resources 5 | import android.text.Spannable 6 | import android.text.TextPaint 7 | import android.text.style.URLSpan 8 | import android.text.style.UnderlineSpan 9 | import android.view.LayoutInflater 10 | import android.view.View 11 | import android.view.ViewGroup 12 | import android.widget.ImageView 13 | import androidx.annotation.LayoutRes 14 | import androidx.core.content.ContextCompat 15 | import com.bumptech.glide.Glide 16 | import com.bumptech.glide.request.RequestOptions 17 | import work.upstarts.editorjskit.R 18 | import work.upstarts.editorjskit.models.data.EJImageData 19 | import work.upstarts.editorjskit.ui.theme.EJStyle 20 | 21 | fun ViewGroup.inflate(@LayoutRes layoutRes: Int, attachToRoot: Boolean = false): View { 22 | return LayoutInflater.from(context).inflate(layoutRes, this, attachToRoot) 23 | } 24 | 25 | fun ImageView.loadImage(url: String?, data: EJImageData) { 26 | url?.let { 27 | Glide.with(context) 28 | .load(url) 29 | .apply { 30 | if (data.stretched) { 31 | centerCrop() 32 | } 33 | val imageHeight = data.file.height 34 | val imageWidth = data.file.width 35 | if (imageHeight != null && imageWidth != null) { 36 | apply(RequestOptions().override(imageWidth, imageHeight)) 37 | } 38 | } 39 | .centerInside() 40 | .into(this) 41 | } 42 | } 43 | 44 | val Int.dp: Int 45 | get() = (this * Resources.getSystem().displayMetrics.density).toInt() 46 | 47 | fun Spannable.applyThemeForUrlSpans(theme: EJStyle?, context: Context) { 48 | for (u in getSpans(0, length, URLSpan::class.java)) { 49 | setSpan(object : UnderlineSpan() { 50 | override fun updateDrawState(tp: TextPaint) { 51 | tp.isUnderlineText = false //todo make it configurable from theme 52 | tp.color = theme?.linkColor ?: 53 | ContextCompat.getColor(context, R.color.link_color) 54 | } 55 | }, getSpanStart(u), getSpanEnd(u), 0) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /editorjskit/src/main/java/work/upstarts/editorjskit/models/EJAbstractCustomBlock.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts.editorjskit.models 2 | 3 | import work.upstarts.editorjskit.models.data.EJData 4 | 5 | open class EJAbstractCustomBlock( 6 | val type: EJAbstractBlockType, 7 | val data: Class 8 | ) -------------------------------------------------------------------------------- /editorjskit/src/main/java/work/upstarts/editorjskit/models/EJBlock.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts.editorjskit.models 2 | 3 | import work.upstarts.editorjskit.models.data.* 4 | 5 | interface EJBlock{ 6 | val type: EJAbstractBlockType 7 | val data: EJData 8 | } 9 | 10 | class EJCustomBlock( 11 | override val type: EJAbstractBlockType, 12 | override val data: EJData 13 | ): EJBlock 14 | 15 | data class EJHeaderBlock( 16 | override val type: EJAbstractBlockType, 17 | override val data: EJHeaderData 18 | ): EJBlock 19 | 20 | data class EJParagraphBlock( 21 | override val type: EJAbstractBlockType, 22 | override val data: EJParagraphData 23 | ): EJBlock 24 | 25 | data class EJListBlock( 26 | override val type: EJAbstractBlockType, 27 | override val data: EJListData 28 | ): EJBlock 29 | 30 | data class EJDelimiterBlock( 31 | override val type: EJAbstractBlockType, 32 | override val data: EJData 33 | ): EJBlock 34 | 35 | data class EJImageBlock( 36 | override val type: EJAbstractBlockType, 37 | override val data: EJImageData 38 | ): EJBlock 39 | 40 | data class EJRawHtmlBlock( 41 | override val type: EJAbstractBlockType, 42 | override val data: EJRawHtmlData 43 | ): EJBlock 44 | 45 | data class EJTableBlock( 46 | override val type: EJAbstractBlockType, 47 | override val data: EJTableData 48 | ): EJBlock -------------------------------------------------------------------------------- /editorjskit/src/main/java/work/upstarts/editorjskit/models/EJBlockType.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts.editorjskit.models 2 | 3 | interface EJAbstractBlockType { 4 | val jsonName: String 5 | } 6 | 7 | enum class EJBlockType(override val jsonName: String) : EJAbstractBlockType { 8 | HEADER("header"), 9 | PARAGRAPH("paragraph"), 10 | LIST("list"), 11 | DELIMITER("delimiter"), 12 | IMAGE("image"), 13 | TABLE("table"), 14 | RAW_HTML("rawTool"); 15 | 16 | companion object { 17 | fun fromString(jsonName: String): EJBlockType { 18 | return when (jsonName){ 19 | "header" -> HEADER 20 | "paragraph" -> PARAGRAPH 21 | "list" -> LIST 22 | "delimiter" -> DELIMITER 23 | "image" -> IMAGE 24 | "table" -> TABLE 25 | "rawTool" -> RAW_HTML 26 | else -> throw IllegalArgumentException(jsonName) 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /editorjskit/src/main/java/work/upstarts/editorjskit/models/HeadingLevel.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts.editorjskit.models 2 | 3 | enum class HeadingLevel(val value: Int) { 4 | h1(1), 5 | h2(2), 6 | h3(3), 7 | h4(4), 8 | h5(5), 9 | h6(6) 10 | } -------------------------------------------------------------------------------- /editorjskit/src/main/java/work/upstarts/editorjskit/models/Margins.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts.editorjskit.models 2 | 3 | class Margins { 4 | 5 | var deviderMargin: MarginData? = null 6 | val headerMargin: HashMap? = HashMap() 7 | var imageMargin: MarginData? = null 8 | var listMargin: MarginData? = null 9 | var paragraphMargin: MarginData? = null 10 | var rawHtmlMargin: MarginData? = null 11 | var tableMargin: MarginData? = null 12 | var bulletMargin: MarginData? = null 13 | var listTextItemMargin: MarginData? = null 14 | 15 | data class MarginData(val marginLeft: Int, val marginTop: Int, val marginRight: Int, val marginBottom: Int) 16 | 17 | fun setHeaderMargin (marginLeft: Int, marginTop: Int, marginRight: Int, marginBottom: Int, headerType: HeadingLevel) { 18 | headerMargin?.set(headerType.value, MarginData(marginLeft, marginTop, marginRight, marginBottom)) 19 | } 20 | 21 | fun setParagraphMargin (marginLeft: Int, marginTop: Int, marginRight: Int, marginBottom: Int) { 22 | paragraphMargin = MarginData(marginLeft, marginTop, marginRight, marginBottom) 23 | } 24 | 25 | fun setImageMargin (marginLeft: Int, marginTop: Int, marginRight: Int, marginBottom: Int) { 26 | imageMargin = MarginData(marginLeft, marginTop, marginRight, marginBottom) 27 | } 28 | 29 | fun setBulletMargin (marginLeft: Int, marginTop: Int, marginRight: Int, marginBottom: Int) { 30 | bulletMargin = MarginData(marginLeft, marginTop, marginRight, marginBottom) 31 | } 32 | 33 | fun setListTextItemMargin (marginLeft: Int, marginTop: Int, marginRight: Int, marginBottom: Int) { 34 | listTextItemMargin = MarginData(marginLeft, marginTop, marginRight, marginBottom) 35 | } 36 | 37 | fun setListMargin (marginLeft: Int, marginTop: Int, marginRight: Int, marginBottom: Int) { 38 | listMargin = MarginData(marginLeft, marginTop, marginRight, marginBottom) 39 | } 40 | 41 | fun setHtmlMargin (marginLeft: Int, marginTop: Int, marginRight: Int, marginBottom: Int) { 42 | rawHtmlMargin = MarginData(marginLeft, marginTop, marginRight, marginBottom) 43 | } 44 | 45 | fun setTableMargin (marginLeft: Int, marginTop: Int, marginRight: Int, marginBottom: Int) { 46 | tableMargin = MarginData(marginLeft, marginTop, marginRight, marginBottom) 47 | } 48 | 49 | fun setDeviderMargin (marginLeft: Int, marginTop: Int, marginRight: Int, marginBottom: Int) { 50 | deviderMargin = MarginData(marginLeft, marginTop, marginRight, marginBottom) 51 | } 52 | } -------------------------------------------------------------------------------- /editorjskit/src/main/java/work/upstarts/editorjskit/models/data/EJData.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts.editorjskit.models.data 2 | 3 | abstract class EJData 4 | -------------------------------------------------------------------------------- /editorjskit/src/main/java/work/upstarts/editorjskit/models/data/EJDelimiterData.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts.editorjskit.models.data 2 | 3 | class EJDelimiterData(): EJData() -------------------------------------------------------------------------------- /editorjskit/src/main/java/work/upstarts/editorjskit/models/data/EJHeaderData.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts.editorjskit.models.data 2 | 3 | data class EJHeaderData( 4 | val text: String, 5 | val level: Int 6 | ): EJData() -------------------------------------------------------------------------------- /editorjskit/src/main/java/work/upstarts/editorjskit/models/data/EJImageData.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts.editorjskit.models.data 2 | 3 | 4 | data class EJImageData( 5 | val file: ImageFile, 6 | val caption: String?, 7 | val withBorder: Boolean, 8 | val stretched: Boolean, 9 | val withBackground: Boolean 10 | ): EJData() 11 | 12 | data class ImageFile( 13 | val url: String, 14 | val width: Int?, 15 | val height: Int? 16 | ) -------------------------------------------------------------------------------- /editorjskit/src/main/java/work/upstarts/editorjskit/models/data/EJListData.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts.editorjskit.models.data 2 | 3 | data class EJListData( 4 | val style: ListType, 5 | val items: List 6 | ): EJData() 7 | 8 | enum class ListType(private val jsonName: String) { 9 | UNORDERED("unordered"), 10 | ORDERED("ordered"); 11 | 12 | override fun toString() = jsonName 13 | 14 | companion object { 15 | fun fromStyle(style: String) = entries.first { it.jsonName == style } 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /editorjskit/src/main/java/work/upstarts/editorjskit/models/data/EJParagraphData.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts.editorjskit.models.data 2 | 3 | 4 | data class EJParagraphData( 5 | val text: String 6 | ): EJData() -------------------------------------------------------------------------------- /editorjskit/src/main/java/work/upstarts/editorjskit/models/data/EJRawHtmlData.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts.editorjskit.models.data 2 | 3 | data class EJRawHtmlData( 4 | val html: String 5 | ): EJData() -------------------------------------------------------------------------------- /editorjskit/src/main/java/work/upstarts/editorjskit/models/data/EJTableData.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts.editorjskit.models.data 2 | 3 | data class EJTableData( 4 | val content: List> 5 | ): EJData() -------------------------------------------------------------------------------- /editorjskit/src/main/java/work/upstarts/editorjskit/ui/EJAdapter.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts.editorjskit.ui 2 | 3 | import android.annotation.SuppressLint 4 | import androidx.recyclerview.widget.DiffUtil 5 | import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter 6 | import work.upstarts.editorjskit.EJKit 7 | import work.upstarts.editorjskit.models.EJBlock 8 | import work.upstarts.editorjskit.ui.adapterdelegates.* 9 | import work.upstarts.editorjskit.ui.theme.EJStyle 10 | 11 | 12 | open class EditorJsAdapter( 13 | style: EJStyle? = EJKit.ejStyle, 14 | diffCallback: DiffUtil.ItemCallback? = null 15 | ) : AsyncListDifferDelegationAdapter(diffCallback ?: DIFF_CALLBACK) { 16 | init { 17 | initDelegates(style) 18 | } 19 | 20 | fun initDelegates(style: EJStyle?) { 21 | delegatesManager 22 | .addDelegate(HeaderAdapterDelegate(style)) 23 | .addDelegate(ParagraphAdapterDelegate(style)) 24 | .addDelegate(DividerAdapterDelegate(style)) 25 | .addDelegate(ImageAdapterDelegate(style)) 26 | .addDelegate(ListAdapterDelegate(style)) 27 | .addDelegate(TableAdapterDelegate(style)) 28 | .addDelegate(RawHtmlAdapterDelegate(style)) 29 | } 30 | } 31 | 32 | private val DIFF_CALLBACK = object : DiffUtil.ItemCallback() { 33 | 34 | override fun areItemsTheSame(oldItem: Any, newItem: Any): Boolean { 35 | if (oldItem === newItem) return true 36 | return if (newItem is EJBlock && oldItem is EJBlock) { 37 | oldItem.data == newItem.data 38 | } else false 39 | } 40 | 41 | @SuppressLint("DiffUtilEquals") 42 | override fun areContentsTheSame(oldItem: Any, newItem: Any) = oldItem == newItem 43 | } -------------------------------------------------------------------------------- /editorjskit/src/main/java/work/upstarts/editorjskit/ui/adapterdelegates/DividerAdapterDelegate.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts.editorjskit.ui.adapterdelegates 2 | 3 | import android.view.View 4 | import android.view.ViewGroup 5 | import android.widget.TextView 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.hannesdorfmann.adapterdelegates4.AdapterDelegate 8 | import work.upstarts.editorjskit.R 9 | import work.upstarts.editorjskit.environment.inflate 10 | import work.upstarts.editorjskit.models.EJBlock 11 | import work.upstarts.editorjskit.models.EJBlockType 12 | import work.upstarts.editorjskit.ui.theme.EJStyle 13 | 14 | class DividerAdapterDelegate( 15 | private val theme: EJStyle? = null 16 | ) : AdapterDelegate>() { 17 | override fun isForViewType(items: MutableList, position: Int): Boolean { 18 | return items[position] is EJBlock && (items[position] as EJBlock).type == EJBlockType.DELIMITER 19 | } 20 | 21 | override fun onBindViewHolder( 22 | items: MutableList, 23 | position: Int, 24 | holder: RecyclerView.ViewHolder, 25 | payloads: MutableList 26 | ) = (holder as ViewHolder).bind() 27 | 28 | 29 | override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder { 30 | val view = parent.inflate(R.layout.item_divider) 31 | val dividerView: TextView = view.findViewById(R.id.ejDivider) 32 | 33 | theme?.applyDividerStyle(dividerView, view) 34 | return ViewHolder(view) 35 | } 36 | 37 | private inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { 38 | fun bind() { 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /editorjskit/src/main/java/work/upstarts/editorjskit/ui/adapterdelegates/HeaderAdapterDelegate.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts.editorjskit.ui.adapterdelegates 2 | 3 | import android.graphics.Paint 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.hannesdorfmann.adapterdelegates4.AdapterDelegate 8 | import work.upstarts.editorjskit.R 9 | import work.upstarts.editorjskit.environment.inflate 10 | import work.upstarts.editorjskit.models.EJBlock 11 | import work.upstarts.editorjskit.models.EJBlockType 12 | import work.upstarts.editorjskit.models.EJHeaderBlock 13 | import work.upstarts.editorjskit.ui.theme.EJStyle 14 | import work.upstarts.editorjskit.ui.views.HeaderTextView 15 | 16 | class HeaderAdapterDelegate( 17 | private val theme: EJStyle? = null 18 | ) : AdapterDelegate>() { 19 | 20 | var items: MutableList? = null 21 | 22 | override fun isForViewType(items: MutableList, position: Int): Boolean { 23 | return items[position] is EJBlock && (items[position] as EJBlock).type == EJBlockType.HEADER 24 | } 25 | 26 | override fun onBindViewHolder( 27 | items: MutableList, 28 | position: Int, 29 | holder: RecyclerView.ViewHolder, 30 | payloads: MutableList 31 | ) = (holder as ViewHolder).bind(items[position] as EJHeaderBlock) 32 | 33 | 34 | override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder { 35 | val view = parent.inflate(R.layout.item_header) 36 | return ViewHolder(view) 37 | } 38 | 39 | fun applyHeaderTheme(view: View, headerInitializer: (EJStyle, Paint) -> Unit) { 40 | theme?.let { 41 | val headerTextView: HeaderTextView = view.findViewById(R.id.headerTv) 42 | 43 | headerTextView.apply { 44 | headerInitializer(it, paint) 45 | } 46 | } 47 | } 48 | 49 | private inner class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) { 50 | private lateinit var headerBlock: EJHeaderBlock 51 | private val headerTextView: HeaderTextView = view.findViewById(R.id.headerTv) 52 | 53 | fun bind(headerBlock: EJHeaderBlock) { 54 | this.headerBlock = headerBlock 55 | 56 | headerTextView.text = headerBlock.data.text 57 | 58 | applyHeaderTheme(view) { theme, paint -> 59 | theme.applyHeadingStyle(view, paint, headerBlock.data.level) 60 | } 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /editorjskit/src/main/java/work/upstarts/editorjskit/ui/adapterdelegates/ImageAdapterDelegate.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts.editorjskit.ui.adapterdelegates 2 | 3 | import android.view.View 4 | import android.view.ViewGroup 5 | import android.widget.ImageView 6 | import android.widget.TextView 7 | import androidx.recyclerview.widget.RecyclerView 8 | import com.hannesdorfmann.adapterdelegates4.AdapterDelegate 9 | import work.upstarts.editorjskit.R 10 | import work.upstarts.editorjskit.environment.inflate 11 | import work.upstarts.editorjskit.environment.loadImage 12 | import work.upstarts.editorjskit.models.EJBlock 13 | import work.upstarts.editorjskit.models.EJBlockType 14 | import work.upstarts.editorjskit.models.EJImageBlock 15 | import work.upstarts.editorjskit.ui.theme.EJStyle 16 | 17 | class ImageAdapterDelegate( 18 | private val theme: EJStyle? = null 19 | ) : AdapterDelegate>() { 20 | override fun isForViewType(items: MutableList, position: Int): Boolean { 21 | return items[position] is EJBlock && (items[position] as EJBlock).type == EJBlockType.IMAGE 22 | } 23 | 24 | override fun onBindViewHolder( 25 | items: MutableList, 26 | position: Int, 27 | holder: RecyclerView.ViewHolder, 28 | payloads: MutableList 29 | ) = (holder as ViewHolder).bind(items[position] as EJImageBlock) 30 | 31 | override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder { 32 | val view = parent.inflate(R.layout.item_image) 33 | return ViewHolder(view) 34 | } 35 | 36 | private inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { 37 | private lateinit var imageBlock: EJImageBlock 38 | 39 | private val imageView: ImageView = view.findViewById(R.id.imageView) 40 | private val imageCaption: TextView = view.findViewById(R.id.imageCaption) 41 | 42 | fun bind(paragraphBlock: EJImageBlock) { 43 | this.imageBlock = paragraphBlock 44 | with(itemView) { 45 | val data = paragraphBlock.data 46 | theme?.applyImageStyle(this, data) 47 | imageView.loadImage(data.file.url, data) 48 | val caption = data.caption 49 | if (caption != null && caption.isNotEmpty()) { 50 | imageCaption.text = caption 51 | imageCaption.visibility = View.VISIBLE 52 | } 53 | } 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /editorjskit/src/main/java/work/upstarts/editorjskit/ui/adapterdelegates/ListAdapterDelegate.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts.editorjskit.ui.adapterdelegates 2 | 3 | import android.view.View 4 | import android.view.ViewGroup 5 | import android.widget.LinearLayout 6 | import android.widget.TextView 7 | import androidx.recyclerview.widget.RecyclerView 8 | import com.hannesdorfmann.adapterdelegates4.AdapterDelegate 9 | import work.upstarts.editorjskit.R 10 | import work.upstarts.editorjskit.environment.inflate 11 | import work.upstarts.editorjskit.models.EJBlock 12 | import work.upstarts.editorjskit.models.EJBlockType 13 | import work.upstarts.editorjskit.models.EJListBlock 14 | import work.upstarts.editorjskit.models.data.EJListData 15 | import work.upstarts.editorjskit.models.data.ListType 16 | import work.upstarts.editorjskit.ui.theme.EJStyle 17 | 18 | class ListAdapterDelegate( 19 | private val theme: EJStyle? = null 20 | ) : AdapterDelegate>() { 21 | override fun isForViewType(items: MutableList, position: Int): Boolean { 22 | return items[position] is EJBlock && (items[position] as EJBlock).type == EJBlockType.LIST 23 | } 24 | 25 | override fun onBindViewHolder( 26 | items: MutableList, 27 | position: Int, 28 | holder: RecyclerView.ViewHolder, 29 | payloads: MutableList 30 | ) = (holder as ViewHolder).bind(items[position] as EJListBlock) 31 | 32 | 33 | override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder { 34 | val view = parent.inflate(R.layout.item_list) 35 | return ViewHolder(view) 36 | } 37 | 38 | private inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { 39 | private lateinit var listData: EJListData 40 | var prefix = "" 41 | 42 | private val containerLayout: LinearLayout = view.findViewById(R.id.container) 43 | 44 | fun bind(listBlock: EJListBlock) { 45 | this.listData = listBlock.data 46 | containerLayout.removeAllViews() 47 | theme?.applyListMargin(itemView) 48 | listData.items.forEachIndexed { index, s -> 49 | if (listData.style == ListType.ORDERED) 50 | prefix = "${index + 1}." 51 | inflateSectionTitle(s, itemView, prefix) 52 | } 53 | } 54 | 55 | private fun inflateSectionTitle(text: String, itemView: View, prefix: String) { 56 | containerLayout.inflate(R.layout.item_list_row).also { 57 | it.findViewWithTag("listRowTv").apply { 58 | tag = null 59 | val listText = "$prefix $text" 60 | setText(listText) 61 | val bulletView = it.findViewById(R.id.bulletView) 62 | if (prefix.isEmpty()) { 63 | bulletView.visibility = View.VISIBLE 64 | } 65 | 66 | theme?.applyListItemStyle(bulletView, this) 67 | } 68 | 69 | (itemView as ViewGroup).addView(it) 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /editorjskit/src/main/java/work/upstarts/editorjskit/ui/adapterdelegates/ParagraphAdapterDelegate.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts.editorjskit.ui.adapterdelegates 2 | 3 | import android.text.Spannable 4 | import android.text.method.LinkMovementMethod 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.TextView 8 | import androidx.core.text.parseAsHtml 9 | import androidx.recyclerview.widget.RecyclerView 10 | import com.hannesdorfmann.adapterdelegates4.AdapterDelegate 11 | import work.upstarts.editorjskit.R 12 | import work.upstarts.editorjskit.environment.applyThemeForUrlSpans 13 | import work.upstarts.editorjskit.environment.inflate 14 | import work.upstarts.editorjskit.models.EJBlock 15 | import work.upstarts.editorjskit.models.EJBlockType 16 | import work.upstarts.editorjskit.models.EJParagraphBlock 17 | import work.upstarts.editorjskit.ui.theme.EJStyle 18 | 19 | class ParagraphAdapterDelegate( 20 | private val theme: EJStyle? = null 21 | ) : AdapterDelegate>() { 22 | override fun isForViewType(items: MutableList, position: Int): Boolean { 23 | return items[position] is EJBlock && (items[position] as EJBlock).type == EJBlockType.PARAGRAPH 24 | } 25 | 26 | override fun onBindViewHolder( 27 | items: MutableList, 28 | position: Int, 29 | holder: RecyclerView.ViewHolder, 30 | payloads: MutableList 31 | ) = (holder as ViewHolder).bind(items[position] as EJParagraphBlock) 32 | 33 | 34 | override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder { 35 | val view = parent.inflate(R.layout.item_paragraph) 36 | 37 | theme?.let { 38 | view.apply { 39 | it.applyParagraphStyle(this, DEFAULT_MARGIN_PARAGRAF) 40 | } 41 | } 42 | return ViewHolder(view) 43 | } 44 | 45 | private inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { 46 | private lateinit var headerBlock: EJParagraphBlock 47 | 48 | private val paragraphTextView: TextView = view.findViewById(R.id.paragraphTv) 49 | 50 | fun bind(paragraphBlock: EJParagraphBlock) { 51 | this.headerBlock = paragraphBlock 52 | with(itemView) { 53 | val text = paragraphBlock.data.text.parseAsHtml() as Spannable 54 | text.applyThemeForUrlSpans(theme, context) 55 | paragraphTextView.text = text 56 | paragraphTextView.movementMethod = LinkMovementMethod.getInstance() 57 | } 58 | } 59 | } 60 | } 61 | 62 | const val DEFAULT_MARGIN_PARAGRAF = 16 -------------------------------------------------------------------------------- /editorjskit/src/main/java/work/upstarts/editorjskit/ui/adapterdelegates/RawHtmlAdapterDelegate.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts.editorjskit.ui.adapterdelegates 2 | 3 | import android.text.Spannable 4 | import android.text.method.LinkMovementMethod 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.TextView 8 | import androidx.core.text.parseAsHtml 9 | import androidx.recyclerview.widget.RecyclerView 10 | import com.hannesdorfmann.adapterdelegates4.AdapterDelegate 11 | import work.upstarts.editorjskit.R 12 | import work.upstarts.editorjskit.environment.applyThemeForUrlSpans 13 | import work.upstarts.editorjskit.environment.inflate 14 | import work.upstarts.editorjskit.models.EJBlock 15 | import work.upstarts.editorjskit.models.EJBlockType 16 | import work.upstarts.editorjskit.models.EJRawHtmlBlock 17 | import work.upstarts.editorjskit.ui.theme.EJStyle 18 | 19 | class RawHtmlAdapterDelegate( 20 | private val theme: EJStyle? = null 21 | ) : AdapterDelegate>() { 22 | 23 | override fun isForViewType(items: MutableList, position: Int): Boolean { 24 | return items[position] is EJBlock && (items[position] as EJBlock).type == EJBlockType.RAW_HTML 25 | } 26 | 27 | override fun onBindViewHolder( 28 | items: MutableList, 29 | position: Int, 30 | holder: RecyclerView.ViewHolder, 31 | payloads: MutableList 32 | ) = (holder as ViewHolder).bind(items[position] as EJRawHtmlBlock) 33 | 34 | 35 | override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder { 36 | val view = parent.inflate(R.layout.item_paragraph) 37 | 38 | val paragraphTextView: TextView = view.findViewById(R.id.paragraphTv) 39 | theme?.let { 40 | paragraphTextView.apply { 41 | it.applyParagraphStyle(this, DEFAULT_MARGIN_HTML) 42 | } 43 | } 44 | 45 | return ViewHolder(view) 46 | } 47 | 48 | 49 | private inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { 50 | private lateinit var rawHtmlBlock: EJRawHtmlBlock 51 | 52 | private val paragraphTextView: TextView = view.findViewById(R.id.paragraphTv) 53 | 54 | fun bind(rawHtmlBlock: EJRawHtmlBlock) { 55 | this.rawHtmlBlock = rawHtmlBlock 56 | with(itemView) { 57 | val text = rawHtmlBlock.data.html.parseAsHtml() as Spannable 58 | text.applyThemeForUrlSpans(theme, context) 59 | paragraphTextView.text = text 60 | paragraphTextView.movementMethod = LinkMovementMethod.getInstance() 61 | } 62 | } 63 | } 64 | } 65 | 66 | const val DEFAULT_MARGIN_HTML = 16 -------------------------------------------------------------------------------- /editorjskit/src/main/java/work/upstarts/editorjskit/ui/adapterdelegates/TableAdapterDelegate.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts.editorjskit.ui.adapterdelegates 2 | 3 | import android.view.View 4 | import android.view.ViewGroup 5 | import android.widget.TableLayout 6 | import android.widget.TableRow 7 | import androidx.appcompat.widget.AppCompatTextView 8 | import androidx.recyclerview.widget.RecyclerView 9 | import com.hannesdorfmann.adapterdelegates4.AdapterDelegate 10 | import work.upstarts.editorjskit.R 11 | import work.upstarts.editorjskit.environment.inflate 12 | import work.upstarts.editorjskit.models.EJBlock 13 | import work.upstarts.editorjskit.models.EJBlockType 14 | import work.upstarts.editorjskit.models.EJTableBlock 15 | import work.upstarts.editorjskit.ui.theme.EJStyle 16 | 17 | class TableAdapterDelegate( 18 | private val theme: EJStyle? = null 19 | ) : AdapterDelegate>() { 20 | override fun isForViewType(items: MutableList, position: Int): Boolean { 21 | return items[position] is EJBlock && (items[position] as EJBlock).type == EJBlockType.TABLE 22 | } 23 | 24 | override fun onBindViewHolder( 25 | items: MutableList, 26 | position: Int, 27 | holder: RecyclerView.ViewHolder, 28 | payloads: MutableList 29 | ) = (holder as ViewHolder).bind(items[position] as EJTableBlock) 30 | 31 | 32 | override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder { 33 | val view = parent.inflate(R.layout.item_table) 34 | 35 | return ViewHolder(view) 36 | } 37 | 38 | private inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { 39 | private lateinit var tableBlock: EJTableBlock 40 | 41 | fun bind(tableBlock: EJTableBlock) { 42 | this.tableBlock = tableBlock 43 | 44 | (itemView as ViewGroup).removeAllViews() 45 | tableBlock.data.content.forEach { 46 | val tableRow = TableRow(itemView.context).apply { 47 | layoutParams = TableRow.LayoutParams() 48 | } 49 | 50 | it.forEach { 51 | val column = tableRow.inflate(R.layout.item_column) 52 | (column as AppCompatTextView).apply { 53 | text = it 54 | theme?.applyTableColumnBackground(this) 55 | } 56 | tableRow.addView(column ) 57 | } 58 | 59 | (itemView as TableLayout).addView(tableRow) 60 | } 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /editorjskit/src/main/java/work/upstarts/editorjskit/ui/theme/EJStyle.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts.editorjskit.ui.theme 2 | 3 | import android.content.Context 4 | import android.graphics.Paint 5 | import android.graphics.Typeface 6 | import android.graphics.drawable.ColorDrawable 7 | import android.view.View 8 | import android.widget.FrameLayout 9 | import android.widget.ImageView 10 | import android.widget.TextView 11 | import androidx.annotation.* 12 | import androidx.annotation.IntRange 13 | import androidx.constraintlayout.widget.ConstraintLayout 14 | import androidx.core.content.ContextCompat 15 | import androidx.core.view.setPadding 16 | import androidx.core.view.updateLayoutParams 17 | import androidx.core.view.updatePadding 18 | import work.upstarts.editorjskit.R 19 | import work.upstarts.editorjskit.environment.dp 20 | import work.upstarts.editorjskit.models.HeadingLevel 21 | import work.upstarts.editorjskit.models.Margins 22 | import work.upstarts.editorjskit.models.data.EJImageData 23 | import work.upstarts.editorjskit.ui.views.HeaderTextView 24 | import java.util.* 25 | import kotlin.collections.HashMap 26 | 27 | 28 | /** 29 | * Class to hold *theming* information for rending. 30 | */ 31 | open class EJStyle protected constructor(builder: Builder) { 32 | 33 | val linkColor: Int? = builder.linkColor 34 | 35 | // used in quote, lists 36 | val blockPadding: Int = builder.blockMargin 37 | val margins: Margins = builder.margins 38 | 39 | // by default uses text color (applied for un-ordered lists & ordered (bullets & numbers) 40 | protected val listItemColor: Int = builder.listItemColor 41 | protected val listBulletColor: Int = builder.listBulletColor 42 | 43 | protected val bulletDrawable: Int = builder.bulletDrawableRes 44 | protected val bulletHeight: Int? = builder.bulletHeight 45 | protected val bulletWidth: Int? = builder.bulletWidth 46 | 47 | // by default - main text color 48 | protected val paragraphTextColor: Int = builder.paragraphTextColor 49 | 50 | 51 | protected val headingTextColor: Int = builder.headingTextColor 52 | 53 | protected val paragraphBackgroundColor: Int = builder.paragraphBackgroundColor 54 | 55 | // by default Typeface.MONOSPACE 56 | protected val paragraphTypeface: Typeface? = builder.paragraphTypeface 57 | protected val listTextItemTypeface: Typeface? = builder.listTextItemTypeface 58 | 59 | // by default a bit (how much?!) smaller than normal text 60 | // applied ONLY if default typeface was used, otherwise, not applied 61 | protected val paragraphTextSize: Int = builder.paragraphTextSize 62 | protected val listTextItemTextSize: Float? = builder.listTextItemTextSize 63 | 64 | // by default, whatever typeface is set on the TextView 65 | protected val headingTypeface: Typeface? = builder.headingTypeface 66 | 67 | protected val headingTypefaceMap: HashMap = builder.headingTypefaceMap 68 | protected val headingFontStyleMap: HashMap = builder.headingFontStyleMap 69 | protected val headingColorsMap: HashMap = builder.headingColorsMap 70 | 71 | // by default, use standard values (see HEADING_SIZES for values). 72 | // this library supports 6 heading sizes, so make sure the array you pass here has 6 elements. 73 | protected val headingTextSizeMultipliers: FloatArray? = builder.headingTextSizes 74 | protected val headingTextMarginsMultipliers: IntArray? = builder.headingTextMargins 75 | 76 | protected val delimiterColor: Int = builder.delimiterColor 77 | 78 | protected val delimiterHeight: Int = builder.delimiterBreakHeight 79 | 80 | protected val tableColumnDrawableRes: Int = builder.tableColumnDrawableRes 81 | protected val tableColumnTextColor: Int = builder.tableColumnTextColor 82 | 83 | protected val imageBackground: Int = builder.imageBackground 84 | protected val imageBorder: Int = builder.imageBorder 85 | 86 | fun applyTableColumnBackground(view: TextView) { 87 | if (tableColumnDrawableRes != 0) { 88 | view.background = ContextCompat.getDrawable(view.context, tableColumnDrawableRes) 89 | } 90 | if (tableColumnTextColor != 0) { 91 | view.setTextColor(tableColumnTextColor) 92 | } 93 | } 94 | 95 | fun applyListItemStyle(bulletView: View, textView: TextView) { 96 | listTextItemTypeface?.let { 97 | textView.typeface = it 98 | } 99 | 100 | listTextItemTextSize?.let { 101 | textView.textSize = it 102 | } 103 | 104 | if (listItemColor != 0) { 105 | textView.setTextColor(listItemColor) 106 | } 107 | 108 | if (listBulletColor != 0) { 109 | bulletView.background.setTint(listBulletColor) 110 | } 111 | 112 | if (bulletDrawable != 0) { 113 | bulletView.background = ContextCompat.getDrawable(bulletView.context, bulletDrawable) 114 | } 115 | 116 | bulletHeight?.let { 117 | val params = bulletView.layoutParams 118 | params.height = it 119 | bulletView.layoutParams = params 120 | } 121 | 122 | bulletWidth?.let { 123 | val params = bulletView.layoutParams 124 | params.width = it 125 | bulletView.layoutParams = params 126 | } 127 | 128 | 129 | margins.apply { 130 | bulletMargin?.let { 131 | setListItemMargin(bulletView, it) 132 | } 133 | 134 | listTextItemMargin?.let { 135 | setListItemMargin(textView, it) 136 | } 137 | } 138 | } 139 | 140 | private fun setListItemMargin(view: View, marginData: Margins.MarginData) { 141 | val params = view.layoutParams as ConstraintLayout.LayoutParams 142 | marginData.apply { 143 | params.setMargins(marginLeft, marginTop, marginRight, marginBottom) 144 | } 145 | view.layoutParams = params 146 | } 147 | 148 | 149 | fun applyParagraphStyle(baseView: View, margin: Int) { 150 | applyParagraphTextStyle(baseView) 151 | applyParagraphMargin(baseView, margin) 152 | } 153 | 154 | fun applyParagraphTextStyle(baseView: View) { 155 | val paragraphTextView: TextView = baseView.findViewById(R.id.paragraphTv) 156 | 157 | if (paragraphTextColor != 0) { 158 | paragraphTextView.setTextColor(paragraphTextColor) 159 | } 160 | 161 | if (paragraphTypeface != null) { 162 | paragraphTextView.typeface = paragraphTypeface 163 | } 164 | 165 | if (paragraphTextSize > 0) { 166 | paragraphTextView.textSize = paragraphTextSize.toFloat() 167 | } 168 | 169 | applyBackgroundColor(paragraphTextView) 170 | applyBlockPadding(paragraphTextView) 171 | } 172 | 173 | private fun applyParagraphMargin(view: View, defaulMargin: Int) { 174 | val margins = margins.paragraphMargin 175 | if (margins != null) { 176 | applyViewMargins(margins, view) 177 | } else { 178 | view.updatePadding( 179 | view.paddingLeft, 180 | defaulMargin.dp, 181 | view.paddingRight, 182 | view.paddingBottom 183 | ) 184 | } 185 | } 186 | 187 | private fun applyBlockPadding(textView: TextView) { 188 | textView.setPadding(blockPadding) 189 | } 190 | 191 | private fun applyBackgroundColor(textView: TextView) { 192 | if (paragraphBackgroundColor != 0) { 193 | textView.background = ColorDrawable(paragraphBackgroundColor) 194 | } 195 | } 196 | 197 | 198 | fun applyHeadingStyle(view: View, paint: Paint, @IntRange(from = 1, to = 6) level: Int) { 199 | val headerTextView: HeaderTextView = view.findViewById(R.id.headerTv) 200 | 201 | applyHeadingTextStyle(paint, level) 202 | applyHeadingTextSize(headerTextView, level) 203 | applyHeadingMargin(view, level) 204 | applyFontStyle(headerTextView, level) 205 | applyHeadingTextColor(headerTextView, level) 206 | } 207 | 208 | private fun applyHeadingTextStyle(paint: Paint, headerLevel: Int) { 209 | if (headingTypefaceMap[headerLevel] != null) { 210 | paint.typeface = headingTypefaceMap[headerLevel] 211 | } else { 212 | if (headingTypeface == null) { 213 | paint.isFakeBoldText = true 214 | } else { 215 | paint.typeface = headingTypeface 216 | } 217 | } 218 | } 219 | 220 | fun applyHeadingTextColor(headerTv: HeaderTextView, @IntRange(from = 1, to = 6) level: Int) { 221 | if (headingTextColor != 0) { 222 | headerTv.setTextColor(headingTextColor) 223 | } 224 | headingColorsMap[level]?.let { 225 | headerTv.setTextColor(it) 226 | } 227 | } 228 | 229 | fun applyHeadingTextSize(headerTv: HeaderTextView, @IntRange(from = 1, to = 6) level: Int) { 230 | val textSizes = headingTextSizeMultipliers ?: HEADING_SIZES 231 | 232 | if (textSizes.size >= level) { 233 | headerTv.setHeaderLevel(textSizes[level - 1]) 234 | } else { 235 | throw IllegalStateException( 236 | String.format( 237 | Locale.US, 238 | "Supplied heading level: %d is invalid, where configured heading sizes are: `%s`", 239 | level, textSizes.contentToString() 240 | ) 241 | ) 242 | } 243 | } 244 | 245 | fun applyHeadingMargin(view: View, @IntRange(from = 1, to = 6) level: Int) { 246 | if (margins.headerMargin?.get(level) == null) { 247 | applyDefaultHeadingMargin(view, level) 248 | } else { 249 | margins.headerMargin[level]?.let { applyViewMargins(it, view) } 250 | } 251 | } 252 | 253 | fun applyDefaultHeadingMargin(view: View, @IntRange(from = 1, to = 6) level: Int) { 254 | val marginSizes = headingTextMarginsMultipliers ?: HEADING_TOP_MARGINS_DEFAULT 255 | if (marginSizes.size >= level) { 256 | view.updatePadding( 257 | view.paddingLeft, 258 | marginSizes[level - 1].dp, 259 | view.paddingRight, 260 | view.paddingBottom 261 | ) 262 | } else { 263 | throw IllegalStateException( 264 | String.format( 265 | Locale.US, 266 | "Supplied heading level: %d is invalid, where configured heading sizes are: `%s`", 267 | level, marginSizes.contentToString() 268 | ) 269 | ) 270 | } 271 | } 272 | 273 | fun applyDefaultImageMargin(view: View, data: EJImageData) { 274 | if (data.withBackground) 275 | view.updatePadding(view.paddingLeft, 32.dp, view.paddingRight, view.paddingBottom) 276 | } 277 | 278 | fun applyDividerStyle(dividerView: TextView, container: View) { 279 | applyDelimiterColor(dividerView) 280 | applyDelimiterHeight(dividerView) 281 | applyDelimiterMargins(container) 282 | } 283 | 284 | private fun applyDelimiterColor(dividerView: TextView) { 285 | if (delimiterColor != 0) { 286 | dividerView.background = ColorDrawable(delimiterColor) 287 | } 288 | } 289 | 290 | fun applyDelimiterHeight(dividerView: TextView) { 291 | dividerView.updateLayoutParams { 292 | height = delimiterHeight 293 | } 294 | } 295 | 296 | fun applyDelimiterMargins(dividerContainer: View) { 297 | margins.deviderMargin?.let { applyViewMargins(it, dividerContainer) } 298 | } 299 | 300 | fun applyImageStyle(view: View, data: EJImageData) { 301 | val imageView: ImageView = view.findViewById(R.id.imageView) 302 | 303 | applyImageRes(imageView, data) 304 | applyImageMargin(view, data) 305 | } 306 | 307 | fun applyImageRes(imageView: ImageView, data: EJImageData) { 308 | if (imageBackground != 0 && data.withBackground) { 309 | imageView.background = imageView.context.getDrawable(imageBackground) 310 | } 311 | 312 | if (imageBorder != 0 && data.withBorder) { 313 | imageView.background = imageView.context.getDrawable(imageBorder) 314 | } 315 | } 316 | 317 | fun applyViewMargins(it: Margins.MarginData, view: View) { 318 | view.updatePadding(it.marginLeft.dp, it.marginTop.dp, it.marginRight.dp, it.marginBottom.dp) 319 | } 320 | 321 | fun applyImageMargin(view: View, data: EJImageData) { 322 | val margins = margins.imageMargin 323 | if (margins != null) { 324 | applyViewMargins(margins, view) 325 | } else { 326 | applyDefaultImageMargin(view, data) 327 | } 328 | } 329 | 330 | fun applyListMargin(view: View) { 331 | margins.apply { 332 | listMargin?.let { 333 | applyViewMargins(it, view) 334 | } 335 | } 336 | } 337 | 338 | fun applyFontStyle(headerTv: HeaderTextView, level: Int) { 339 | headingFontStyleMap[level]?.let { headerTv.setTypeface(headerTv.typeface, it) } 340 | } 341 | 342 | class Builder { 343 | internal var margins = Margins() 344 | internal var linkColor: Int? = null 345 | internal var headingTextColor: Int = 0 346 | internal var blockMargin: Int = 0 347 | internal var listItemColor: Int = 0 348 | internal var listBulletColor: Int = 0 349 | internal var bulletDrawableRes: Int = 0 350 | internal var bulletWidth: Int? = null 351 | internal var bulletHeight: Int? = null 352 | internal var paragraphTextColor: Int = 0 353 | internal var paragraphBackgroundColor: Int = 0 354 | internal var paragraphTypeface: Typeface? = null 355 | internal var listTextItemTypeface: Typeface? = null 356 | internal val headingTypefaceMap: HashMap = HashMap() 357 | internal val headingFontStyleMap: HashMap = HashMap() 358 | internal val headingColorsMap: HashMap = HashMap() 359 | internal var paragraphTextSize: Int = 0 360 | internal var listTextItemTextSize: Float? = null 361 | internal var headingTypeface: Typeface? = null 362 | internal var headingTextSizes: FloatArray? = null 363 | internal var headingTextMargins: IntArray? = null 364 | internal var delimiterColor: Int = 0 365 | internal var delimiterBreakHeight = 1 366 | internal var tableColumnDrawableRes: Int = 0 367 | internal var tableColumnTextColor: Int = 0 368 | internal var imageBackground: Int = 0 369 | internal var imageBorder: Int = 0 370 | 371 | internal constructor() 372 | 373 | internal constructor(theme: EJStyle) { 374 | this.linkColor = theme.linkColor 375 | this.blockMargin = theme.blockPadding 376 | this.listItemColor = theme.listItemColor 377 | this.listBulletColor = theme.listBulletColor 378 | this.bulletDrawableRes = theme.bulletDrawable 379 | this.paragraphTextColor = theme.paragraphTextColor 380 | this.paragraphBackgroundColor = theme.paragraphBackgroundColor 381 | this.paragraphTypeface = theme.paragraphTypeface 382 | this.paragraphTextSize = theme.paragraphTextSize 383 | this.headingTypeface = theme.headingTypeface 384 | this.headingTextSizes = theme.headingTextSizeMultipliers 385 | this.headingTextMargins = theme.headingTextMarginsMultipliers 386 | this.delimiterColor = theme.delimiterColor 387 | this.delimiterBreakHeight = theme.delimiterHeight 388 | this.tableColumnTextColor = theme.tableColumnTextColor 389 | this.imageBackground = theme.imageBackground 390 | this.imageBorder = theme.imageBorder 391 | this.headingTextColor = theme.headingTextColor 392 | } 393 | 394 | fun linkColor(@ColorInt linkColor: Int): Builder { 395 | this.linkColor = linkColor 396 | return this 397 | } 398 | 399 | fun blockMargin(@Px blockMargin: Int): Builder { 400 | this.blockMargin = blockMargin 401 | return this 402 | } 403 | 404 | 405 | fun listItemColor(@ColorInt listItemColor: Int): Builder { 406 | this.listItemColor = listItemColor 407 | return this 408 | } 409 | 410 | fun listBulletColor(@ColorInt listItemColor: Int): Builder { 411 | this.listBulletColor = listItemColor 412 | return this 413 | } 414 | 415 | fun bulletDrawableRes(@DrawableRes drawableRes: Int): Builder { 416 | this.bulletDrawableRes = drawableRes 417 | return this 418 | } 419 | 420 | fun bulletSize(height: Int?, width: Int?): Builder { 421 | this.bulletWidth = width 422 | this.bulletHeight = height 423 | return this 424 | } 425 | 426 | fun imageBackgroundRes(@DrawableRes imageBackground: Int): Builder { 427 | this.imageBackground = imageBackground 428 | return this 429 | } 430 | 431 | fun imageBorderRes(@DrawableRes imageBorder: Int): Builder { 432 | this.imageBorder = imageBorder 433 | return this 434 | } 435 | 436 | fun tableColumnDrawableRes(@DrawableRes drawableRes: Int): Builder { 437 | this.tableColumnDrawableRes = drawableRes 438 | return this 439 | } 440 | 441 | fun tableColumnTextColor(@ColorInt color: Int): Builder { 442 | this.tableColumnTextColor = color 443 | return this 444 | } 445 | 446 | fun paragraphTextColor(@ColorInt paragraphTextColor: Int): Builder { 447 | this.paragraphTextColor = paragraphTextColor 448 | return this 449 | } 450 | 451 | fun paragraphBackgroundColor(@ColorInt paragraphBackgroundColor: Int): Builder { 452 | this.paragraphBackgroundColor = paragraphBackgroundColor 453 | return this 454 | } 455 | 456 | fun paragraphTypeface(paragraphTypeface: Typeface): Builder { 457 | this.paragraphTypeface = paragraphTypeface 458 | return this 459 | } 460 | 461 | fun listTextItemTypeFace(paragraphTypeface: Typeface): Builder { 462 | this.listTextItemTypeface = paragraphTypeface 463 | return this 464 | } 465 | 466 | fun paragraphTextSize(@Px paragraphTextSize: Int): Builder { 467 | this.paragraphTextSize = paragraphTextSize 468 | return this 469 | } 470 | 471 | fun listTextItemTextSize(@Px ItemTextSize: Float): Builder { 472 | this.listTextItemTextSize = ItemTextSize 473 | return this 474 | } 475 | 476 | fun headingTypeface(headingTypeface: Typeface): Builder { 477 | this.headingTypeface = headingTypeface 478 | return this 479 | } 480 | 481 | fun headingTypefaceDetailed(headingTypeface: Typeface, level: HeadingLevel): Builder { 482 | this.headingTypefaceMap[level.value] = headingTypeface 483 | return this 484 | } 485 | 486 | fun headingColorDetailed(color: Int, level: HeadingLevel): Builder { 487 | this.headingColorsMap[level.value] = color 488 | return this 489 | } 490 | 491 | /** 492 | * @param headingTextSizes an array of multipliers values for heading elements. 493 | * The base value for this multipliers is TextView\'s text size 494 | */ 495 | fun headingTextSizes(@Size(6) headingTextSizes: FloatArray): Builder { 496 | this.headingTextSizes = headingTextSizes 497 | return this 498 | } 499 | 500 | fun dividerBreakColor(@ColorInt thematicBreakColor: Int): Builder { 501 | this.delimiterColor = thematicBreakColor 502 | return this 503 | } 504 | 505 | fun dividerBreakHeight(thematicBreakHeight: Int): Builder { 506 | this.delimiterBreakHeight = thematicBreakHeight 507 | return this 508 | } 509 | 510 | fun build(): EJStyle { 511 | return EJStyle(this) 512 | } 513 | 514 | fun dividerMargin( 515 | marginLeft: Int, 516 | marginTop: Int, 517 | marginRight: Int, 518 | marginBottom: Int 519 | ): Builder { 520 | this.margins.setDeviderMargin(marginLeft, marginTop, marginRight, marginBottom) 521 | return this 522 | } 523 | 524 | fun headingMargin( 525 | marginLeft: Int, 526 | marginTop: Int, 527 | marginRight: Int, 528 | marginBottom: Int, 529 | headerType: HeadingLevel 530 | ): Builder { 531 | this.margins.setHeaderMargin( 532 | marginLeft, 533 | marginTop, 534 | marginRight, 535 | marginBottom, 536 | headerType 537 | ) 538 | return this 539 | } 540 | 541 | fun imageMargin( 542 | marginLeft: Int, 543 | marginTop: Int, 544 | marginRight: Int, 545 | marginBottom: Int 546 | ): Builder { 547 | this.margins.setImageMargin(marginLeft, marginTop, marginRight, marginBottom) 548 | return this 549 | } 550 | 551 | fun bulletMargin( 552 | marginLeft: Int, 553 | marginTop: Int, 554 | marginRight: Int, 555 | marginBottom: Int 556 | ): Builder { 557 | this.margins.setBulletMargin(marginLeft, marginTop, marginRight, marginBottom) 558 | return this 559 | } 560 | 561 | fun listTextItemMargin( 562 | marginLeft: Int, 563 | marginTop: Int, 564 | marginRight: Int, 565 | marginBottom: Int 566 | ): Builder { 567 | this.margins.setListTextItemMargin(marginLeft, marginTop, marginRight, marginBottom) 568 | return this 569 | } 570 | 571 | fun paragraphMargin( 572 | marginLeft: Int, 573 | marginTop: Int, 574 | marginRight: Int, 575 | marginBottom: Int 576 | ): Builder { 577 | this.margins.setParagraphMargin(marginLeft, marginTop, marginRight, marginBottom) 578 | return this 579 | } 580 | 581 | fun rawHtmlMargin( 582 | marginLeft: Int, 583 | marginTop: Int, 584 | marginRight: Int, 585 | marginBottom: Int 586 | ): Builder { 587 | this.margins.setHtmlMargin(marginLeft, marginTop, marginRight, marginBottom) 588 | return this 589 | } 590 | 591 | fun tableMargin( 592 | marginLeft: Int, 593 | marginTop: Int, 594 | marginRight: Int, 595 | marginBottom: Int 596 | ): Builder { 597 | this.margins.setTableMargin(marginLeft, marginTop, marginRight, marginBottom) 598 | return this 599 | } 600 | 601 | fun listMargin( 602 | marginLeft: Int, 603 | marginTop: Int, 604 | marginRight: Int, 605 | marginBottom: Int 606 | ): Builder { 607 | this.margins.setListMargin(marginLeft, marginTop, marginRight, marginBottom) 608 | return this 609 | } 610 | 611 | fun headingFontStyleDetailed(style: Int, level: HeadingLevel): Builder { 612 | this.headingFontStyleMap[level.value] = style 613 | return this 614 | } 615 | } 616 | 617 | companion object { 618 | 619 | /** 620 | * Factory method to obtain an instance of [EJStyle] with all values as defaults 621 | * 622 | * @param context Context in order to resolve defaults 623 | * @return [EJStyle] instance 624 | * @see .builderWithDefaults 625 | * @since 1.0.0 626 | */ 627 | fun create(context: Context): EJStyle { 628 | return builderWithDefaults(context).build() 629 | } 630 | 631 | /** 632 | * Create an **empty** instance of [Builder] with no default values applied 633 | */ 634 | fun emptyBuilder(): Builder { 635 | return Builder() 636 | } 637 | 638 | /** 639 | * Factory method to create a [Builder] instance and initialize it with values 640 | * from supplied [EJStyle] 641 | * 642 | * @param copyFrom [EJStyle] to copy values from 643 | * @return [Builder] instance 644 | * @see .builderWithDefaults 645 | * @since 1.0.0 646 | */ 647 | fun builder(copyFrom: EJStyle): Builder { 648 | return Builder(copyFrom) 649 | } 650 | 651 | /** 652 | * Factory method to obtain a [Builder] instance initialized with default values taken 653 | * from current application theme. 654 | * 655 | * @param context Context to obtain default styling values (colors, etc) 656 | * @return [Builder] instance 657 | * @since 1.0.0 658 | */ 659 | fun builderWithDefaults(context: Context): Builder { 660 | return Builder() 661 | .bulletDrawableRes(R.drawable.list_circle) 662 | .tableColumnDrawableRes(R.drawable.table_content_cell_bg) 663 | .imageBackgroundRes(R.drawable.image_background) 664 | } 665 | 666 | private val HEADING_SIZES = floatArrayOf(22f, 20f, 17f, 16f, 14f, 12f) 667 | private val HEADING_TOP_MARGINS_DEFAULT = intArrayOf(24, 32, 32, 14, 12, 8) 668 | } 669 | } -------------------------------------------------------------------------------- /editorjskit/src/main/java/work/upstarts/editorjskit/ui/views/HeaderTextView.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts.editorjskit.ui.views 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import androidx.appcompat.widget.AppCompatTextView 6 | 7 | class HeaderTextView @JvmOverloads constructor( 8 | context: Context, 9 | attributeSet: AttributeSet? = null, 10 | defStyleAttr: Int = 0 11 | ) : AppCompatTextView(context, attributeSet, defStyleAttr) { 12 | 13 | private val density = context.resources.displayMetrics.density 14 | 15 | fun setHeaderLevel(level: Float) { 16 | val headerSize = density * level 17 | if (paint.textSize != headerSize) 18 | paint.textSize = headerSize 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /editorjskit/src/main/res/drawable/image_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /editorjskit/src/main/res/drawable/list_circle.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | -------------------------------------------------------------------------------- /editorjskit/src/main/res/drawable/table_content_cell_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /editorjskit/src/main/res/layout/item_column.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /editorjskit/src/main/res/layout/item_divider.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | -------------------------------------------------------------------------------- /editorjskit/src/main/res/layout/item_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | -------------------------------------------------------------------------------- /editorjskit/src/main/res/layout/item_image.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 24 | 25 | -------------------------------------------------------------------------------- /editorjskit/src/main/res/layout/item_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | -------------------------------------------------------------------------------- /editorjskit/src/main/res/layout/item_list_row.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 24 | 25 | 36 | -------------------------------------------------------------------------------- /editorjskit/src/main/res/layout/item_paragraph.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | -------------------------------------------------------------------------------- /editorjskit/src/main/res/layout/item_table.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /editorjskit/src/main/res/values/color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #000000 4 | #6D7A95 5 | #DE000000 6 | #8A000000 7 | 8 | #eee 9 | #AFAFAF 10 | #FF00FF 11 | #258FFB 12 | #808080 13 | -------------------------------------------------------------------------------- /editorjskit/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4dp 4 | 10dp 5 | 3dp 6 | 8dp 7 | 8dp 8 | 16dp 9 | 24dp 10 | 300dp 11 | 17sp 12 | -------------------------------------------------------------------------------- /editorjskit/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | EditorJSKit 3 | * * * 4 | 5 | -------------------------------------------------------------------------------- /editorjskit/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | 3 | #Tools 4 | compileSdk = "34" 5 | targetSdk = "34" 6 | minSdk = "21" 7 | agp = "8.3.2" 8 | kotlin = "1.9.22" 9 | coreKtx = "1.13.0" 10 | junit = "4.13.2" 11 | appcompat = "1.6.1" 12 | constraintlayout = "2.1.4" 13 | adapterdelegates = "4.3.2" 14 | glide = "4.16.0" 15 | publishVersion = "2.0.1" 16 | gson = "2.10.1" 17 | moshi = "1.15.1" 18 | recycler = "1.3.2" 19 | 20 | [libraries] 21 | androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } 22 | junit = { group = "junit", name = "junit", version.ref = "junit" } 23 | androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } 24 | androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } 25 | adapter-delegates = { group = "com.hannesdorfmann", name = "adapterdelegates4", version.ref = "adapterdelegates" } 26 | glide = { group = "com.github.bumptech.glide", name = "glide", version.ref = "glide" } 27 | gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" } 28 | moshi = { group = "com.squareup.moshi", name = "moshi-kotlin", version.ref = "moshi" } 29 | moshi-adapters = { group = "com.squareup.moshi", name = "moshi-adapters", version.ref = "moshi" } 30 | recyclerview = { group = "androidx.recyclerview", name = "recyclerview", version.ref = "recycler" } 31 | 32 | [plugins] 33 | android-application = { id = "com.android.application", version.ref = "agp" } 34 | jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } 35 | 36 | -------------------------------------------------------------------------------- /gradle/publish.gradle: -------------------------------------------------------------------------------- 1 | apply from: rootProject.file("gradle/versions.gradle") 2 | 3 | publish { 4 | userOrg = 'heckslam' 5 | groupId = 'com.github.upstarts' 6 | publishVersion = "${versions.publishVersion}" 7 | licences = ['MIT'] 8 | desc = 'Module adds gson adapter for EditorJS library' 9 | website = 'https://github.com/Upstarts/editor.js-kit-android' 10 | } 11 | -------------------------------------------------------------------------------- /gradle/versions.gradle: -------------------------------------------------------------------------------- 1 | ext.versions = [ 2 | compileSdk : 34, 3 | targetSdk : 34, 4 | minSdk : 21, 5 | kotlin : '1.9.22', 6 | androidx_appcompat : '1.6.1', 7 | androidx_constraint : '2.1.4', 8 | androidx_recyclerview: '1.3.2', 9 | androidx_core_ktx : '1.13.0', 10 | junit : '4.13.2', 11 | gson : '2.10.1', 12 | moshi : '1.15.1', 13 | adapter_delegates : '4.3.2', 14 | glide : '4.16.0', 15 | publishVersion : '2.0.1' 16 | ] 17 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upstarts/editor.js-kit-android/469242ccf57936494a8c460d41bf2260c9062de6/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Apr 07 13:57:06 MSK 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /gsonparser/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /gsonparser/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | id("kotlin-android") 4 | id("maven-publish") 5 | } 6 | 7 | android { 8 | namespace = "work.upstarts.gsonparser" 9 | compileSdk = libs.versions.compileSdk.get().toInt() 10 | defaultConfig { 11 | minSdk = libs.versions.minSdk.get().toInt() 12 | consumerProguardFiles("consumer-rules.pro") 13 | } 14 | buildTypes { 15 | release { 16 | isMinifyEnabled = false 17 | } 18 | } 19 | lint { 20 | warning.add("InvalidPackage") 21 | } 22 | compileOptions { 23 | sourceCompatibility = JavaVersion.VERSION_17 24 | targetCompatibility = JavaVersion.VERSION_17 25 | } 26 | kotlinOptions { 27 | jvmTarget = "17" 28 | } 29 | publishing { 30 | multipleVariants { 31 | allVariants() 32 | withJavadocJar() 33 | withSourcesJar() 34 | } 35 | } 36 | } 37 | 38 | dependencies { 39 | api(libs.gson) 40 | implementation(project(":editorjskit")) 41 | } 42 | 43 | afterEvaluate { 44 | publishing { 45 | publications { 46 | create("mavenRelease") { 47 | groupId = "io.github.upstarts" 48 | artifactId = "ejkit-gson" 49 | version = libs.versions.publishVersion.get() 50 | 51 | from(components["release"]) 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /gsonparser/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | ##---------------Begin: proguard configuration for Gson ---------- 2 | # Gson uses generic type information stored in a class file when working with fields. Proguard 3 | # removes such information by default, so configure it to keep all of it. 4 | -keepattributes Signature 5 | 6 | # For using GSON @Expose annotation 7 | -keepattributes *Annotation* 8 | 9 | # Gson specific classes 10 | -dontwarn sun.misc.** 11 | #-keep class com.google.gson.stream.** { *; } 12 | 13 | # Application classes that will be serialized/deserialized over Gson 14 | -keep class sun.misc.Unsafe { *; } 15 | 16 | # Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory, 17 | # JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter) 18 | -keep class * implements com.google.gson.TypeAdapter 19 | -keep class * implements com.google.gson.TypeAdapterFactory 20 | -keep class * implements com.google.gson.JsonSerializer 21 | -keep class * implements com.google.gson.JsonDeserializer 22 | 23 | # Prevent R8 from leaving Data object members always null 24 | -keepclassmembers,allowobfuscation class * { 25 | @com.google.gson.annotations.SerializedName ; 26 | } 27 | 28 | ##---------------End: proguard configuration for Gson ---------- -------------------------------------------------------------------------------- /gsonparser/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /gsonparser/src/main/java/work/upstarts/gsonparser/EJDeserializer.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts.gsonparser 2 | 3 | import com.google.gson.* 4 | import work.upstarts.editorjskit.EJKit 5 | import work.upstarts.editorjskit.models.* 6 | import work.upstarts.editorjskit.models.data.EJDelimiterData 7 | import work.upstarts.editorjskit.models.data.EJListData 8 | import work.upstarts.editorjskit.models.data.ListType 9 | import java.lang.reflect.Type 10 | 11 | class EJDeserializer : JsonDeserializer> { 12 | 13 | @Throws(JsonParseException::class) 14 | override fun deserialize( 15 | json: JsonElement, 16 | typeOfT: Type, 17 | context: JsonDeserializationContext 18 | ): MutableList { 19 | 20 | val resultBlockList = mutableListOf() 21 | 22 | val blocks = json.asJsonArray 23 | 24 | for (block in blocks) { 25 | val blockObj = block.asJsonObject 26 | val type = blockObj.get("type").asString 27 | val data = blockObj.get("data").asJsonObject 28 | 29 | try { 30 | val typeClass = EJBlockType.fromString(type) 31 | 32 | typeClass.let { 33 | val parsedBlock = when (typeClass) { 34 | EJBlockType.IMAGE -> EJImageBlock(it, fromJson(data)) 35 | EJBlockType.DELIMITER -> EJDelimiterBlock(it, EJDelimiterData()) 36 | EJBlockType.PARAGRAPH -> EJParagraphBlock(it, fromJson(data)) 37 | EJBlockType.HEADER -> EJHeaderBlock(it, fromJson(data)) 38 | EJBlockType.LIST -> EJListBlock( 39 | it, 40 | EJListData( 41 | ListType.fromStyle(data["style"].asString), 42 | data["items"].asJsonArray.map { item -> item.asString } 43 | ) 44 | ) 45 | EJBlockType.RAW_HTML -> EJRawHtmlBlock(it, fromJson(data)) 46 | EJBlockType.TABLE -> EJTableBlock(it, fromJson(data)) 47 | } 48 | 49 | resultBlockList.add(parsedBlock) 50 | } 51 | } catch (e: IllegalArgumentException) { 52 | EJKit.getCustomBlocks().find { it.type.jsonName == type }?.let { 53 | val parsedData = Gson().fromJson(data, it.data) 54 | resultBlockList.add(EJCustomBlock(it.type, parsedData)) 55 | } 56 | e.printStackTrace() 57 | } 58 | 59 | } 60 | 61 | return resultBlockList 62 | 63 | } 64 | } 65 | 66 | inline fun fromJson(value: JsonObject): T { 67 | return Gson().fromJson(value, T::class.java) 68 | } 69 | -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk17 3 | -------------------------------------------------------------------------------- /moshiparser/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /moshiparser/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | id("kotlin-android") 4 | id("maven-publish") 5 | } 6 | 7 | buildscript { 8 | repositories { 9 | mavenCentral() 10 | } 11 | dependencies { 12 | // No dependencies needed here (unless you use buildscript blocks) 13 | } 14 | } 15 | 16 | android { 17 | namespace = "work.upstarts.moshiparser" 18 | compileSdk = libs.versions.compileSdk.get().toInt() 19 | 20 | defaultConfig { 21 | minSdk = libs.versions.minSdk.get().toInt() 22 | consumerProguardFiles("consumer-rules.pro") 23 | } 24 | buildTypes { 25 | release { 26 | isMinifyEnabled = false 27 | } 28 | } 29 | compileOptions { 30 | sourceCompatibility = JavaVersion.VERSION_17 31 | targetCompatibility = JavaVersion.VERSION_17 32 | } 33 | kotlinOptions { 34 | jvmTarget = "17" 35 | } 36 | publishing { 37 | multipleVariants { 38 | allVariants() 39 | withJavadocJar() 40 | withSourcesJar() 41 | } 42 | } 43 | } 44 | 45 | dependencies { 46 | api(project(":editorjskit")) 47 | implementation(libs.moshi) 48 | implementation(libs.moshi.adapters) 49 | 50 | testImplementation(libs.junit) 51 | } 52 | 53 | afterEvaluate { 54 | publishing { 55 | publications { 56 | create("mavenRelease") { 57 | groupId = "io.github.upstarts" 58 | artifactId = "ejkit-moshi" 59 | version = libs.versions.publishVersion.get() 60 | 61 | from(components["release"]) 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /moshiparser/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | # JSR 305 annotations are for embedding nullability information. 2 | -dontwarn javax.annotation.** 3 | 4 | -keepclasseswithmembers class * { 5 | @com.squareup.moshi.* ; 6 | } 7 | 8 | -keep @com.squareup.moshi.JsonQualifier @interface * 9 | 10 | # Enum field names are used by the integrated EnumJsonAdapter. 11 | # values() is synthesized by the Kotlin compiler and is used by EnumJsonAdapter indirectly 12 | # Annotate enums with @JsonClass(generateAdapter = false) to use them with Moshi. 13 | -keepclassmembers @com.squareup.moshi.JsonClass class * extends java.lang.Enum { 14 | ; 15 | **[] values(); 16 | } 17 | 18 | # Keep helper method to avoid R8 optimisation that would keep all Kotlin Metadata when unwanted 19 | -keepclassmembers class com.squareup.moshi.internal.Util { 20 | private static java.lang.String getKotlinMetadataClassName(); 21 | } 22 | 23 | # Keep ToJson/FromJson-annotated methods 24 | -keepclassmembers class * { 25 | @com.squareup.moshi.FromJson ; 26 | @com.squareup.moshi.ToJson ; 27 | } 28 | -------------------------------------------------------------------------------- /moshiparser/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /moshiparser/src/main/java/work/upstarts/moshiparser/EJAbstractBlockTypeAdapter.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts.moshiparser 2 | 3 | import com.squareup.moshi.FromJson 4 | import com.squareup.moshi.ToJson 5 | import work.upstarts.editorjskit.models.EJAbstractBlockType 6 | import work.upstarts.editorjskit.models.EJBlockType 7 | 8 | class EJAbstractBlockTypeAdapter{ 9 | @FromJson 10 | fun fromJson(type: String): EJAbstractBlockType { 11 | return EJBlockType.fromString(type) 12 | } 13 | 14 | @ToJson 15 | fun toJson(type: EJAbstractBlockType): String { 16 | return type.jsonName 17 | } 18 | } -------------------------------------------------------------------------------- /moshiparser/src/main/java/work/upstarts/moshiparser/EJBlockAdapter.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts.moshiparser 2 | 3 | import com.squareup.moshi.* 4 | import work.upstarts.editorjskit.EJKit 5 | import work.upstarts.editorjskit.models.* 6 | import work.upstarts.editorjskit.models.data.* 7 | 8 | class EJBlockAdapter( 9 | private val moshi: Moshi 10 | ) { 11 | @FromJson 12 | fun fromJson(jsonData: Map): EJBlock? { 13 | val type = jsonData["type"].toString() 14 | val data = jsonData["data"] ?: throw IllegalStateException("Data cannot be null") 15 | try { 16 | return when (val ejBlockType = EJBlockType.fromString(type)) { 17 | EJBlockType.HEADER -> { 18 | EJHeaderBlock( 19 | ejBlockType, 20 | moshi.adapter(EJHeaderData::class.java).fromJsonValue(data) 21 | ?: throw IllegalStateException("Cannot deserialize ${EJHeaderData::class.java.name}") 22 | ) 23 | } 24 | EJBlockType.PARAGRAPH -> { 25 | EJParagraphBlock( 26 | ejBlockType, 27 | moshi.adapter(EJParagraphData::class.java).fromJsonValue(data) 28 | ?: throw IllegalStateException("Cannot deserialize ${EJParagraphData::class.java.name}") 29 | ) 30 | } 31 | EJBlockType.LIST -> { 32 | EJListBlock( 33 | ejBlockType, 34 | moshi.adapter(EJListData::class.java).fromJsonValue(data) 35 | ?: throw IllegalStateException("Cannot deserialize ${EJListData::class.java.name}") 36 | ) 37 | } 38 | EJBlockType.DELIMITER -> { 39 | EJDelimiterBlock(ejBlockType, EJDelimiterData()) 40 | } 41 | EJBlockType.IMAGE -> { 42 | EJImageBlock( 43 | ejBlockType, 44 | moshi.adapter(EJImageData::class.java).fromJsonValue(data) 45 | ?: throw IllegalStateException("Cannot deserialize ${EJImageData::class.java.name}") 46 | ) 47 | } 48 | EJBlockType.TABLE -> { 49 | EJTableBlock( 50 | ejBlockType, 51 | moshi.adapter(EJTableData::class.java).fromJsonValue(data) 52 | ?: throw IllegalStateException("Cannot deserialize ${EJTableData::class.java.name}") 53 | ) 54 | } 55 | EJBlockType.RAW_HTML -> { 56 | EJRawHtmlBlock( 57 | ejBlockType, 58 | moshi.adapter(EJRawHtmlData::class.java).fromJsonValue(data) 59 | ?: throw IllegalStateException("Cannot deserialize ${EJRawHtmlData::class.java.name}") 60 | ) 61 | } 62 | } 63 | } catch (exception: IllegalArgumentException) { 64 | EJKit.getCustomBlocks().find { it.type.jsonName == type }?.let { 65 | val ejBlockData = moshi.adapter(it.data) 66 | .fromJsonValue(data) ?: throw IllegalStateException("Cannot deserialize ${it.data.name}") 67 | 68 | return EJCustomBlock( 69 | it.type, 70 | ejBlockData 71 | ) 72 | } 73 | 74 | exception.printStackTrace() 75 | } 76 | 77 | return null 78 | } 79 | 80 | @ToJson 81 | fun toJson(block: EJBlock): String? { 82 | TODO("Not implemented") 83 | } 84 | } -------------------------------------------------------------------------------- /moshiparser/src/test/java/work/upstarts/moshiparser/EJBlockAdapterTest.kt: -------------------------------------------------------------------------------- 1 | package work.upstarts.moshiparser 2 | 3 | import com.squareup.moshi.Moshi 4 | import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory 5 | import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory 6 | import org.hamcrest.CoreMatchers.instanceOf 7 | import org.junit.Test 8 | import work.upstarts.editorjskit.models.EJBlock 9 | import work.upstarts.editorjskit.models.EJBlockType 10 | import work.upstarts.editorjskit.models.EJParagraphBlock 11 | 12 | class EJBlockAdapterTest { 13 | 14 | @Test 15 | fun fromJsonWithStandardPolymorphicAdapterSingleParagraph() { 16 | val moshi = Moshi.Builder() 17 | .add(EJAbstractBlockTypeAdapter()) 18 | .add( 19 | PolymorphicJsonAdapterFactory.of(EJBlock::class.java, "type") 20 | .withSubtype(EJParagraphBlock::class.java, EJBlockType.PARAGRAPH.jsonName) 21 | ) 22 | .add(KotlinJsonAdapterFactory()) 23 | .build() 24 | 25 | val result = moshi.adapter(EJBlock::class.java) 26 | .fromJson(singleParagraph) 27 | 28 | instanceOf(EJParagraphBlock::class.java).matches(result) 29 | } 30 | 31 | @Test 32 | fun fromJsonWithEJBlockAdapterSingleParagraph() { 33 | val innerMoshi = Moshi.Builder() 34 | .add(KotlinJsonAdapterFactory()) 35 | .build() 36 | 37 | val moshi = Moshi.Builder() 38 | .add(EJBlockAdapter(innerMoshi)) 39 | .build() 40 | 41 | val result = moshi.adapter(EJBlock::class.java) 42 | .fromJson(singleParagraph) 43 | 44 | instanceOf(EJParagraphBlock::class.java).matches(result) 45 | } 46 | 47 | companion object { 48 | const val singleParagraph = "{\"type\": \"paragraph\",\"data\": {\"text\": \"Lorem ipsum bla bla\" } }" 49 | } 50 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 | 5 | 6 | 7 |

8 | 9 | [![](https://jitpack.io/v/Upstarts/editor.js-kit-android.svg)](https://jitpack.io/#Upstarts/editor.js-kit-android) 10 | ## About 11 | 12 | A non-official Android Framework for [Editor.js](https://editorjs.io) - block styled editor. It's purpose to make easy use of rendering and parsing of blocks. 13 | 14 | Converts clean json blocks data like [this](app/src/main/assets/dummy_data.json) into native views like that 👇 15 | 16 |

17 | 18 |

19 | 20 | #### Supported blocks 21 | * 🎩 Header 22 | * 🥑 Raw HTML 23 | * 📷 Image 24 | * 🖌 Delimiter 25 | * 💌 Paragraph 26 | * 🌿 List 27 | * 📋 Table 28 | 29 | ## Installation 30 | add jitpack repo: 31 | ``` 32 | repositories { 33 | maven { url "https://jitpack.io" } 34 | } 35 | 36 | *.kts 37 | repositories { 38 | maven { setUrl("https://jitpack.io") } 39 | } 40 | ``` 41 | add dependency: 42 | ``` 43 | implementation 'com.github.Upstarts.editor-js-kit-android:ejkit:X.X.X' - look at badge above for latest version 44 | implementation 'com.github.Upstarts.editor-js-kit-android:ejkit-gson:X.X.X' - adds GSON adapter. If you use other library for parsing json, you need to write adapter yourself. 45 | implementation 'com.github.Upstarts.editor-js-kit-android:ejkit-moshi:X.X.X' - adds Moshi adapter. 46 | ``` 47 | 48 | ## Setup 49 | 1. Create adapter instance in your activity/fragment (Style parameter is optional) 50 | ``` 51 | private val rvAdapter: EditorJsAdapter by lazy { 52 | EditorJsAdapter(EJStyle.create(this.applicationContext)) 53 | } 54 | ``` 55 | 2. Set adapter to recyclerview in your layout 56 | 3. Init gson with custom Deserializer 57 | ``` 58 | val blocksType = object : TypeToken>() {}.type 59 | val gson = GsonBuilder() 60 | .registerTypeAdapter(blocksType, EJDeserializer()) 61 | .create() 62 | ``` 63 | 4. Apply received data to adapter 64 | ``` 65 | rvAdapter.items = ejResponse.blocks 66 | ``` 67 | 68 | ## Customizing 69 | You can set style globally via `EJKit.style = ...` for all adapters, or pass `EJStyle` instance for specific adapter. 70 | ``` 71 | EJStyle.builderWithDefaults(applicationContext) 72 | .linkColor(ContextCompat.getColor(this,R.color.default_color)) 73 | .blockMargin(10) 74 | .dividerMargin(10, 10) 75 | .headingMargin(10, 10, HeadingLevel.h1) 76 | .imageMargin(10, 10) 77 | .paragraphMargin(10, 10) 78 | .rawHtmlMargin(10, 10) 79 | .tableMargin(10, 10) 80 | .listItemColor(ContextCompat.getColor(this,R.color.default_color)) 81 | .listBulletColor(ContextCompat.getColor(this,R.color.default_color)) 82 | .bulletSize(6, 6) 83 | .bulletMargin(10, 10, 10, 10) 84 | .listTextItemMargin(10, 10, 10, 10) 85 | .listTextItemTypeFace(ResourcesCompat.getFont(this, R.font.montserrat_bold)!!) 86 | .listTextItemTextSize(18f) 87 | .bulletDrawableRes(R.drawable.list_circle) 88 | .paragraphTextColor(ContextCompat.getColor(this,R.color.default_color)) 89 | .paragraphBackgroundColor(ContextCompat.getColor(this,R.color.default_color)) 90 | .paragraphTypeface(ResourcesCompat.getFont(this, R.font.my_font)!!) 91 | .paragraphTextColor(ContextCompat.getColor(this,R.color.default_color)) 92 | .headingTypeface(ResourcesCompat.getFont(this, R.font.my_font)!!) 93 | .headingTypefaceDetailed(ResourcesCompat.getFont(this, R.font.my_font)!!, HeadingLevel.h1) 94 | .headingFontStyleDetailed(R.style.ParagraphText, HeadingLevel.h1) 95 | .headingColorDetailed(R.color.default_color, HeadingLevel.h1) 96 | .headingTextSizes(floatArrayOf( 12f, 12f, 12f,12f,12f,12f)) 97 | .dividerBreakColor(ContextCompat.getColor(this,R.color.default_color)) 98 | .dividerBreakHeight(10) 99 | .tableColumnDrawableRes(R.drawable.table_content_cell_bg) 100 | .tableColumnTextColor(ContextCompat.getColor(this,R.color.default_color)) 101 | .imageBackgroundRes(R.drawable.image_background) 102 | .imageBorderRes(R.drawable.image_background) 103 | .build() 104 | ``` 105 | 106 | ### Override android style in styles.xml 107 | ``` 108 | 111 | 114 | ``` 115 | 116 | ### Create custom block 117 | If you have block that library does not provide currently, you can add it by yourself. 118 | 1. Create custom block type and extend it from `EJAbstractBlockType` 119 | ``` 120 | enum class CustomBlockType(override val jsonName: String) : EJAbstractBlockType { 121 | TABLE("table") 122 | } 123 | ``` 124 | 2. Create data class for block 125 | ``` 126 | data class EJTableData( 127 | val content: List> 128 | ): EJData() 129 | ``` 130 | 3. And register it 131 | `EJKit.register(EJCustomBlock(CustomB.TABLE, EJTableData::class.java)) 132 | ` 133 | 134 | ## Example 135 | 136 | You can find and test the example in a [sample activity](app/src/main/java/work/upstarts/MainActivity.kt) 137 | 138 | ## Author 139 | 140 | [Upstarts team](https://upstarts.work) 141 | 142 | [Ruslan Aliev](https://github.com/heckslam) - Architecture, Implementation 143 | 144 | [Vadim Popov](https://github.com/PopovVadim) - Architecture draft 145 | -------------------------------------------------------------------------------- /scripts/publish-module.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'maven-publish' 2 | apply plugin: 'signing' 3 | 4 | task androidSourcesJar(type: Jar) { 5 | archiveClassifier.set('sources') 6 | if (project.plugins.findPlugin("com.android.library")) { 7 | // For Android libraries 8 | from android.sourceSets.main.java.srcDirs 9 | from android.sourceSets.main.kotlin.srcDirs 10 | } else { 11 | // For pure Kotlin libraries, in case you have them 12 | from sourceSets.main.java.srcDirs 13 | from sourceSets.main.kotlin.srcDirs 14 | } 15 | } 16 | 17 | artifacts { 18 | archives androidSourcesJar 19 | } 20 | 21 | afterEvaluate { 22 | publishing { 23 | publications { 24 | release(MavenPublication) { 25 | groupId PUBLISH_GROUP_ID 26 | artifactId PUBLISH_ARTIFACT_ID 27 | version PUBLISH_VERSION 28 | 29 | // Two artifacts, the `aar` (or `jar`) and the sources 30 | if (project.plugins.findPlugin("com.android.library")) { 31 | from components.release 32 | } else { 33 | from components.java 34 | } 35 | 36 | artifact androidSourcesJar 37 | pom { 38 | name = PUBLISH_ARTIFACT_ID 39 | description = 'A non-official Android Framework for Editor.js - block styled editor' 40 | url = 'https://github.com/Upstarts/editor.js-kit-android' 41 | licenses { 42 | license { 43 | name = 'MIT' 44 | url = 'https://github.com/Upstarts/editor.js-kit-android/blob/master/LICENSE' 45 | } 46 | } 47 | developers { 48 | developer { 49 | id = 'raliev' 50 | name = 'Ruslan Aliev' 51 | email = 'r.aliev@upstarts.work' 52 | } 53 | } 54 | 55 | scm { 56 | connection = 'scm:github.com/Upstarts/editor.js-kit-android.git' 57 | developerConnection = 'scm:git:ssh://github.com/Upstarts/editor.js-kit-android.git' 58 | url = 'https://github.com/Upstarts/editor.js-kit-android/tree/main' 59 | } 60 | } 61 | } 62 | } 63 | } 64 | } 65 | 66 | signing { 67 | useInMemoryPgpKeys( 68 | rootProject.ext["signing.keyId"], 69 | rootProject.ext["signing.key"], 70 | rootProject.ext["signing.password"], 71 | ) 72 | sign publishing.publications 73 | } 74 | -------------------------------------------------------------------------------- /scripts/publish-root.gradle: -------------------------------------------------------------------------------- 1 | // init from local.properties 2 | ext["signing.keyId"] = '' 3 | ext["signing.password"] = '' 4 | ext["signing.key"] = '' 5 | ext["ossrhUsername"] = '' 6 | ext["ossrhPassword"] = '' 7 | ext["sonatypeStagingProfileId"] = '' 8 | 9 | File secretPropsFile = project.rootProject.file('local.properties') 10 | if (secretPropsFile.exists()) { 11 | // Read local.properties file first if it exists 12 | Properties p = new Properties() 13 | new FileInputStream(secretPropsFile).withCloseable { is -> p.load(is) } 14 | p.each { name, value -> ext[name] = value } 15 | } else { 16 | // Use system environment variables 17 | ext["ossrhUsername"] = System.getenv('OSSRH_USERNAME') 18 | ext["ossrhPassword"] = System.getenv('OSSRH_PASSWORD') 19 | ext["sonatypeStagingProfileId"] = System.getenv('SONATYPE_STAGING_PROFILE_ID') 20 | ext["signing.keyId"] = System.getenv('SIGNING_KEY_ID') 21 | ext["signing.password"] = System.getenv('SIGNING_PASSWORD') 22 | ext["signing.key"] = System.getenv('SIGNING_KEY') 23 | } 24 | 25 | nexusPublishing { 26 | repositories { 27 | sonatype { 28 | stagingProfileId = sonatypeStagingProfileId 29 | username = ossrhUsername 30 | password = ossrhPassword 31 | nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) 32 | snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google { 4 | content { 5 | includeGroupByRegex("com\\.android.*") 6 | includeGroupByRegex("com\\.google.*") 7 | includeGroupByRegex("androidx.*") 8 | } 9 | } 10 | mavenCentral() 11 | gradlePluginPortal() 12 | } 13 | } 14 | dependencyResolutionManagement { 15 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 16 | repositories { 17 | google() 18 | mavenCentral() 19 | } 20 | } 21 | 22 | rootProject.name = "EditorJSKitSample" 23 | include(":app") 24 | include(":editorjskit") 25 | include(":gsonparser") 26 | include(":moshiparser") 27 | --------------------------------------------------------------------------------