Compare commits

...

4 Commits

@ -1,7 +1,8 @@
--- ---
url: https://mary.joefix.it/haunted-intro/ url: https://mary.joefix.it/haunted-intro/
title: hauntED intro title: hauntED Readme
feature_image: https://mary.joefix.it/content/images/size/w1200/2023/11/IMG_20231105_132234.jpg feature_image: https://mary.joefix.it/content/images/size/w1200/2023/11/IMG_20231105_132234.jpg
author: yova@freedomhost.de
--- ---
# hauntED 🚀 # hauntED 🚀
@ -19,16 +20,22 @@ A simple markdown editor for ghost on android. Built with [simple-markdown-edito
- send links from other apps to include as markdown syntax - send links from other apps to include as markdown syntax
- dark mode - dark mode
- a lot of md functionality, like [links](https://git.gugelfrei.de) - a lot of md functionality, like [links](https://git.gugelfrei.de)
- perfect for spantaneous content creation right from your hammock. 🏞️ - perfect for spontaneous content creation right from your hammock. 🏞️
## Recommendations ## Recommendations
as this uses webview, that is the chromium rendering engine adapted as android service, in respect to your privacy, it is advised to install an edited version like bromite or ungoogled webview. Most simple is to just buy a prepared fone [from me](https://gugelfrei.de). as this uses webview, that is the chromium rendering engine adapted as android service, in respect to your privacy, it is advised to install an edited version like bromite or ungoogled webview. Most simple is to just buy a prepared fone [from me](https://gugelfrei.de).
made with ❤️ by yv@wntr.org made with ❤️ by yv@wntr.org ©️ 2024
![Screenshot_20240201-200704_MDEditor.png](https://mary.joefix.it/content/images/2024/02/Screenshot_20240201-200704_MDEditor.png)
![Screenshot_20240201-200737_MDEditor.png](https://mary.joefix.it/content/images/2024/02/Screenshot_20240201-200737_MDEditor.png)
![Screenshot_20240201-201115_MDEditor.png](https://mary.joefix.it/content/images/2024/02/Screenshot_20240201-201115_MDEditor.png)
![Screenshot_20240115-174321_MDEditor.png](https://mary.joefix.it/content/images/2024/01/Screenshot_20240115-174321_MDEditor.png)

@ -3,7 +3,6 @@ package org.wntr.mdeditor
import android.app.Activity import android.app.Activity
import android.app.AlertDialog import android.app.AlertDialog
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.net.Uri.parse import android.net.Uri.parse
@ -37,7 +36,6 @@ import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.ResponseBody import okhttp3.ResponseBody
import okhttp3.ResponseBody.Companion.toResponseBody import okhttp3.ResponseBody.Companion.toResponseBody
import org.json.JSONObject import org.json.JSONObject
import org.wntr.mdeditor.databinding.ActivityMainBinding
import retrofit2.Response import retrofit2.Response
import java.io.BufferedReader import java.io.BufferedReader
import java.io.File import java.io.File
@ -52,7 +50,6 @@ import kotlin.concurrent.fixedRateTimer
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val easyMDEscript = """ private val easyMDEscript = """
const easyMDE = new EasyMDE({ const easyMDE = new EasyMDE({
spellChecker: false, spellChecker: false,
@ -62,69 +59,64 @@ class MainActivity : AppCompatActivity() {
autoDownloadFontAwesome: false, autoDownloadFontAwesome: false,
theme: "solarized", theme: "solarized",
status: [ status: [
{ {
className: "editor-statusbar-left", className: "editor-statusbar-left",
onUpdate: (el) => { onUpdate: (el) => {
el.innerHTML = saveStatus() el.innerHTML = saveStatus()
} }
}, },
{ {
className: "displayName", className: "displayName",
defaultValue: "None", defaultValue: "None",
onUpdate: (el) => { onUpdate: (el) => {
el.innerHTML = displayName() el.innerHTML = displayName()
}, },
}, "lines", "words", "cursor", }, "lines", "words", "cursor",
{ {
className: "editor-statusbar-right", className: "editor-statusbar-right",
onUpdate: (el) => { onUpdate: (el) => {
el.innerHTML = "<i class=\"fa fa-square\"></i>" el.innerHTML = "<i class=\"fa fa-square\"></i>"
} }
} }
], ],
toolbar: [ toolbar: [
{ {
name: "toggleTheme", name: "toggleTheme",
action: toggleTheme, action: toggleTheme,
className: "fa fa-moon", className: "fa fa-moon",
title: "Toggle Theme" title: "Toggle Theme"
}, },
{ {
name: "share", name: "share",
action: shareText, action: shareText,
className: "fa fa-share-nodes", className: "fa fa-share-nodes",
title: "Share" title: "Share"
},"strikethrough", "horizontal-rule","undo", },"strikethrough", "horizontal-rule","undo",
{ {
name: "preview", name: "preview",
action: myPreview, action: myPreview,
className: "fa fa-eye", className: "fa fa-eye",
title: "Preview", title: "Preview",
noDisable: true noDisable: true
},"redo", },"redo",
"bold", "italic","link","code", "bold", "italic","link","code",
{ {
name: "toggle", name: "toggle",
action: toggleBar, action: toggleBar,
className: "fa fa-expand", className: "fa fa-expand",
title: "Toggle Bar", title: "Toggle Bar",
} }
] ]
}); });
""" """
companion object { companion object {
const val CREATE_FILE = 1 const val CREATE_FILE = 1
const val OPEN_FILE = 2 const val OPEN_FILE = 2
const val DELETE_GHOST = 4
var deleteVisible = false var deleteVisible = false
var mdeValue: String = "" var mdeValue: String = ""
var metaData = mdMeta()
var mdToAppend: String = "" var mdToAppend: String = ""
var thisFileUri: Uri? = null
var truncate = false var truncate = false
lateinit var tempFile: File
lateinit var pickMultipleVisualMedia: ActivityResultLauncher<PickVisualMediaRequest> lateinit var pickMultipleVisualMedia: ActivityResultLauncher<PickVisualMediaRequest>
lateinit var ghostSettings: ActivityResultLauncher<Intent> lateinit var ghostSettings: ActivityResultLauncher<Intent>
lateinit var ghostMetaData: ActivityResultLauncher<Intent> lateinit var ghostMetaData: ActivityResultLauncher<Intent>
@ -139,7 +131,6 @@ class MainActivity : AppCompatActivity() {
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
@ -173,19 +164,9 @@ class MainActivity : AppCompatActivity() {
webView.loadUrl("file:///android_res/raw/index.html") webView.loadUrl("file:///android_res/raw/index.html")
val jsi = object { val jsi = object {
@JavascriptInterface
fun getValue(): String {
return mdeValue
}
@JavascriptInterface @JavascriptInterface
fun reportChange(value: String) { fun triggerSaveFile() {
Log.i(javaClass.simpleName, value)
}
@JavascriptInterface
fun triggerSaveFile(value: String) {
mdeValue = value
saveFile() saveFile()
} }
@ -198,11 +179,6 @@ class MainActivity : AppCompatActivity() {
return height return height
}*/ }*/
@JavascriptInterface
fun refresh() {
readFile(thisFileUri!!)
}
@JavascriptInterface @JavascriptInterface
fun triggerDisplayName(): String { fun triggerDisplayName(): String {
return getDisplayName(applicationContext, thisFileUri) return getDisplayName(applicationContext, thisFileUri)
@ -210,12 +186,17 @@ class MainActivity : AppCompatActivity() {
@JavascriptInterface @JavascriptInterface
fun triggerShare(sharedText: String) { fun triggerShare(sharedText: String) {
shareHtml(sharedText) val htmlFile = createHtmlFile(applicationContext, sharedText)
} if (htmlFile == null) return
val htmlUri = FileProvider.getUriForFile(applicationContext, "org.wntr.mdeditor.fileprovider", htmlFile)
@JavascriptInterface
fun triggerGhost(sharedText: String) { with(Intent(Intent.ACTION_SEND)) {
shareGhost(sharedText, ::sendPost) htmlFile?.also {
type = "text/plain"
putExtra(Intent.EXTRA_STREAM, htmlUri)
startActivity(Intent.createChooser(this, "Share html"))
}
}
} }
@JavascriptInterface @JavascriptInterface
@ -225,13 +206,6 @@ class MainActivity : AppCompatActivity() {
return md return md
} }
@JavascriptInterface
fun getCursor(): String {
val cursor = JSONObject(JSONObject(metaData.cursor), arrayOf("ch", "line"))
Log.i(javaClass.simpleName,"delivering cursor: $cursor")
return cursor.toString()
}
@JavascriptInterface @JavascriptInterface
fun isFullscreen() : Boolean{ fun isFullscreen() : Boolean{
return supportActionBar!!.isShowing return supportActionBar!!.isShowing
@ -315,7 +289,19 @@ class MainActivity : AppCompatActivity() {
Log.d(javaClass.simpleName, "AutosaveTimer started.") Log.d(javaClass.simpleName, "AutosaveTimer started.")
return return
} }
loadMetaFromSharedPrefs()
if (!loadMetaFromSharedPrefs(applicationContext)) {
with(AlertDialog.Builder(this)){
setPositiveButton("Open", { dialog, id ->
openFile()
})
setNeutralButton("New", { dialog, id ->
selectFileForSaveAs()
})
show()
}
}
if (intent.data !== null) { if (intent.data !== null) {
readFile(intent.data!!) readFile(intent.data!!)
Log.d(javaClass.simpleName,"Loading on resume from intent: ${thisFileUri}") Log.d(javaClass.simpleName,"Loading on resume from intent: ${thisFileUri}")
@ -377,7 +363,7 @@ class MainActivity : AppCompatActivity() {
intent.putExtra(Intent.EXTRA_STREAM, null as Uri?) intent.putExtra(Intent.EXTRA_STREAM, null as Uri?)
} else if (mimeType == "text" && intent.data != null ) { } else if (mimeType == "text" && intent.data != null ) {
thisFileUri = uri thisFileUri = uri
saveMetaToSharedPrefs() saveMetaToSharedPrefs(applicationContext)
Log.d(javaClass.simpleName,"wanna start app with new txt") Log.d(javaClass.simpleName,"wanna start app with new txt")
} }
} }
@ -705,62 +691,7 @@ class MainActivity : AppCompatActivity() {
return true return true
} }
fun shareHtml(htmlString: String): Boolean { fun openFile() {
try {
createHTMLFile()
} catch (e: Exception) {
Log.d(javaClass.simpleName, "Problem with accessing file\n${e.stackTraceToString()}")
Toast.makeText(
this,
"Problem with accessing file\n$e",
Toast.LENGTH_LONG
).show()
return false
}
try {
FileOutputStream(tempFile).use {
it.write(htmlString.toByteArray())
}
} catch (e: Exception) {
Toast.makeText(
this,
"Error during writing.\n$e",
Toast.LENGTH_LONG
).show()
return false
}
Log.d(javaClass.simpleName, "File saved: ${tempFile.toURI()}")
Toast.makeText(
this,
"HTML output produced.",
Toast.LENGTH_LONG
).show()
val htmlUri = FileProvider.getUriForFile(this, "org.wntr.mdeditor.fileprovider", tempFile)
with(Intent(Intent.ACTION_SEND)) {
tempFile?.also {
type = "text/plain"
putExtra(Intent.EXTRA_STREAM, htmlUri)
startActivity(Intent.createChooser(this, "Share html"))
}
}
return true
}
@Throws(IOException::class)
private fun createHTMLFile() {
// Create an image file name
val storageDir = File(cacheDir, "html")
storageDir.mkdir()
tempFile = File(storageDir.path + "/${getDisplayName(applicationContext, thisFileUri).split(".")[0]}.html")
if (tempFile.exists()) tempFile.delete()
tempFile.createNewFile()
}
fun openFile() {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE) addCategory(Intent.CATEGORY_OPENABLE)
putExtra(DocumentsContract.EXTRA_INITIAL_URI, thisFileUri) putExtra(DocumentsContract.EXTRA_INITIAL_URI, thisFileUri)
@ -774,8 +705,6 @@ class MainActivity : AppCompatActivity() {
startActivityForResult(intent, OPEN_FILE) startActivityForResult(intent, OPEN_FILE)
} }
fun selectFileForSaveAs() { fun selectFileForSaveAs() {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE) addCategory(Intent.CATEGORY_OPENABLE)
@ -837,7 +766,7 @@ class MainActivity : AppCompatActivity() {
Intent.FLAG_GRANT_READ_URI_PERMISSION and Intent.FLAG_GRANT_WRITE_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION and Intent.FLAG_GRANT_WRITE_URI_PERMISSION
) )
thisFileUri = uri thisFileUri = uri
saveMetaToSharedPrefs() saveMetaToSharedPrefs(applicationContext)
if (metaData.metaData.get("url") == null) { if (metaData.metaData.get("url") == null) {
deleteVisible = false deleteVisible = false
invalidateOptionsMenu() invalidateOptionsMenu()
@ -975,7 +904,7 @@ class MainActivity : AppCompatActivity() {
Toast.LENGTH_LONG Toast.LENGTH_LONG
).show() ).show()
textFile.close() textFile.close()
saveMetaToSharedPrefs() saveMetaToSharedPrefs(applicationContext)
Log.i(javaClass.simpleName, "file newly written") Log.i(javaClass.simpleName, "file newly written")
} catch (e: Exception) { } catch (e: Exception) {
Toast.makeText( Toast.makeText(
@ -1136,38 +1065,6 @@ class MainActivity : AppCompatActivity() {
} }
} }
private fun saveMetaToSharedPrefs() {
if (thisFileUri == null) return
Log.d(javaClass.simpleName, "saving to shared prefs cursor: ${metaData.cursor} in file: ${thisFileUri}")
getSharedPreferences("prefs", Context.MODE_PRIVATE)
.edit().apply {
putString("lastFile", thisFileUri.toString())
putString("cursor", metaData.cursor)
apply()
}
}
private fun loadMetaFromSharedPrefs(){
val prefs = getSharedPreferences("prefs", Context.MODE_PRIVATE)
val uriString = prefs.getString("lastFile", "noLastFile")
if (uriString == "noLastFile") {
with(AlertDialog.Builder(this)){
setPositiveButton("Open", { dialog, id ->
openFile()
})
setNeutralButton("New", { dialog, id ->
selectFileForSaveAs()
})
show()
}
} else {
thisFileUri = parse(uriString)
metaData.cursor = prefs.getString("cursor", "nocursor") ?: "{ line: 0, ch: 0, sticky: null }"
Log.i(javaClass.simpleName,"Loaded cursor: ${metaData.cursor}")
}
}
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
try{ try{
@ -1179,7 +1076,7 @@ class MainActivity : AppCompatActivity() {
webView.evaluateJavascript("easyMDE.codemirror.doc.getCursor();") { webView.evaluateJavascript("easyMDE.codemirror.doc.getCursor();") {
metaData.cursor=it metaData.cursor=it
Log.i(javaClass.simpleName,"Cursor: $it") Log.i(javaClass.simpleName,"Cursor: $it")
saveMetaToSharedPrefs() saveMetaToSharedPrefs(applicationContext)
} }
Log.i(javaClass.simpleName, "\"onPause\" durchlaufen") Log.i(javaClass.simpleName, "\"onPause\" durchlaufen")
} }
@ -1189,5 +1086,3 @@ class MainActivity : AppCompatActivity() {
Log.i(javaClass.simpleName, "\"onRestoreInstanceState\" durchlaufen") Log.i(javaClass.simpleName, "\"onRestoreInstanceState\" durchlaufen")
} }
} }

@ -5,6 +5,7 @@ import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.View import android.view.View
import org.wntr.mdeditor.MainActivity.Companion.credManager
import org.wntr.mdeditor.databinding.ActivityMetadataBinding import org.wntr.mdeditor.databinding.ActivityMetadataBinding
class MetadataActivity : AppCompatActivity() { class MetadataActivity : AppCompatActivity() {
@ -15,25 +16,25 @@ class MetadataActivity : AppCompatActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityMetadataBinding.inflate(layoutInflater) binding = ActivityMetadataBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
binding.title.setText(MainActivity.metaData.get("title") ?: getDisplayName(applicationContext, MainActivity.thisFileUri)) binding.title.setText(metaData.get("title") ?: getDisplayName(applicationContext, thisFileUri))
binding.url.setText(MainActivity.metaData.get("url")) binding.url.setText(metaData.get("url"))
binding.featureImage.setText(MainActivity.metaData.get("feature_image")) binding.featureImage.setText(metaData.get("feature_image"))
binding.author.setText(MainActivity.metaData.get("author") ?: MainActivity.credManager.username) binding.author.setText(metaData.get("author") ?: credManager.username)
} }
fun onButtonSaveClick(view: View) { fun onButtonSaveClick(view: View) {
Log.d(javaClass.simpleName, "Getting Metadata:\ntitle:\t${binding.title.text}\nfeature_image:\t${binding.featureImage.text}") Log.d(javaClass.simpleName, "Getting Metadata:\ntitle:\t${binding.title.text}\nfeature_image:\t${binding.featureImage.text}")
MainActivity.metaData.put("title", binding.title.text.toString()) metaData.put("title", binding.title.text.toString())
MainActivity.metaData.put("feature_image", binding.featureImage.text.toString()) metaData.put("feature_image", binding.featureImage.text.toString())
if (binding.author.text.toString() != MainActivity.credManager.username) { if (binding.author.text.toString() != MainActivity.credManager.username) {
var apiHost: String? = null var apiHost: String? = null
if (MainActivity.metaData.get("url") !== null) { if (metaData.get("url") !== null) {
val url = Uri.parse(MainActivity.metaData.get("url")) val url = Uri.parse(metaData.get("url"))
apiHost = url.scheme + "://" + url.host apiHost = url.scheme + "://" + url.host
} else apiHost = MainActivity.credManager.instance } else apiHost = MainActivity.credManager.instance
MainActivity.credManager.saveCredentialsToSharedPrefs(Credentials(apiHost, binding.author.text.toString())) credManager.saveCredentialsToSharedPrefs(Credentials(apiHost, binding.author.text.toString()))
} }
MainActivity.metaData.put("author", binding.author.text.toString()) metaData.put("author", binding.author.text.toString())
MainActivity.readOnResume = false MainActivity.readOnResume = false
finish() finish()
} }

@ -5,11 +5,15 @@ import android.content.Context
import android.net.Uri import android.net.Uri
import android.provider.OpenableColumns import android.provider.OpenableColumns
import android.util.Log import android.util.Log
import android.widget.Toast
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
var thisFileUri: Uri? = null
var metaData = mdMeta()
fun checkURIResource(context: Context, uri: Uri?): Boolean { fun checkURIResource(context: Context, uri: Uri?): Boolean {
val cursor = context.contentResolver.query(uri!!, null, null, null, null) val cursor = context.contentResolver.query(uri!!, null, null, null, null)
val doesExist = cursor != null && cursor.moveToFirst() val doesExist = cursor != null && cursor.moveToFirst()
@ -28,11 +32,55 @@ fun createFileFromStream(ins: InputStream, destination: File?) {
os.flush() os.flush()
} }
} catch (ex: java.lang.Exception) { } catch (ex: java.lang.Exception) {
Log.e("Save File", ex.message!!) Log.e("Save img tmp file", ex.message!!)
ex.printStackTrace() ex.printStackTrace()
} }
} }
fun createHtmlFile(context: Context, htmlString: String): File? {
val TAG="share HTML"
lateinit var htmlFile : File
try {
val storageDir = File(context.cacheDir, "html")
storageDir.mkdir()
htmlFile = File(storageDir.path + "/${getDisplayName(context,thisFileUri)
.split(".")[0]}.html")
if (htmlFile.exists()) htmlFile.delete()
htmlFile.createNewFile()
} catch (e: Exception) {
Log.d(TAG, "Problem with accessing file\n${e.stackTraceToString()}")
Toast.makeText(
context,
"Problem with accessing file\n$e",
Toast.LENGTH_LONG
).show()
return null
}
try {
FileOutputStream(htmlFile).use {
it.write(htmlString.toByteArray())
}
} catch (e: Exception) {
Toast.makeText(
context,
"Error during writing.\n$e",
Toast.LENGTH_LONG
).show()
return null
}
Log.d(TAG, "File saved: ${htmlFile.toURI()}")
Toast.makeText(
context,
"HTML output produced.",
Toast.LENGTH_LONG
).show()
return htmlFile
}
fun deleteCache(context: Context) { fun deleteCache(context: Context) {
try { try {
val dir = File(context.cacheDir, "html") val dir = File(context.cacheDir, "html")
@ -112,3 +160,28 @@ fun queryName(context: Context, uri: Uri): String? {
return name return name
} }
fun saveMetaToSharedPrefs(context: Context) {
if (thisFileUri == null) return
Log.d("saveMetaToSharedPrefs", "saving to shared prefs cursor: ${metaData.cursor} in file: ${thisFileUri}")
context.getSharedPreferences("prefs", Context.MODE_PRIVATE)
.edit().apply {
putString("lastFile", thisFileUri.toString())
putString("cursor", metaData.cursor)
apply()
}
}
fun loadMetaFromSharedPrefs(context: Context): Boolean{
val prefs = context.getSharedPreferences("prefs", Context.MODE_PRIVATE)
val uriString = prefs.getString("lastFile", "noLastFile")
if (uriString == "noLastFile") {
Log.i("loadMetaFromSharedPrefs","No lastfile saved")
return false
} else {
thisFileUri = Uri.parse(uriString)
metaData.cursor = prefs.getString("cursor", "nocursor") ?: "{ line: 0, ch: 0, sticky: null }"
Log.i("loadMetaFromSharedPrefs","Loaded cursor: ${metaData.cursor}")
return true
}
}

@ -1,14 +1,5 @@
function onRead() {
easyMDE.codemirror.doc.setValue(Android.getValue())
easyMDE.codemirror.doc.markClean()
easyMDE.codemirror.focus()
easyMDE.codemirror.doc.setCursor(JSON.parse(Android.getCursor()))
pasteText()
}
function saveFile() { function saveFile() {
if (!easyMDE.codemirror.doc.isClean()) Android.triggerSaveFile(easyMDE.value()) if (!easyMDE.codemirror.doc.isClean()) Android.triggerSaveFile()
easyMDE.codemirror.doc.markClean()
} }
function dispatchCut() { function dispatchCut() {
@ -22,10 +13,7 @@ function myPreview() {
saveFile() saveFile()
easyMDE.togglePreview() easyMDE.togglePreview()
} }
function refresh() {
Android.refresh()
onRead()
}
function displayName() { function displayName() {
if (typeof Android !== 'undefined') return Android.triggerDisplayName() if (typeof Android !== 'undefined') return Android.triggerDisplayName()
else return "NONdroid" else return "NONdroid"

Loading…
Cancel
Save