Compare commits

...

4 Commits

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

@ -5,6 +5,7 @@ import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.View
import org.wntr.mdeditor.MainActivity.Companion.credManager
import org.wntr.mdeditor.databinding.ActivityMetadataBinding
class MetadataActivity : AppCompatActivity() {
@ -15,25 +16,25 @@ class MetadataActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
binding = ActivityMetadataBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.title.setText(MainActivity.metaData.get("title") ?: getDisplayName(applicationContext, MainActivity.thisFileUri))
binding.url.setText(MainActivity.metaData.get("url"))
binding.featureImage.setText(MainActivity.metaData.get("feature_image"))
binding.author.setText(MainActivity.metaData.get("author") ?: MainActivity.credManager.username)
binding.title.setText(metaData.get("title") ?: getDisplayName(applicationContext, thisFileUri))
binding.url.setText(metaData.get("url"))
binding.featureImage.setText(metaData.get("feature_image"))
binding.author.setText(metaData.get("author") ?: credManager.username)
}
fun onButtonSaveClick(view: View) {
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())
MainActivity.metaData.put("feature_image", binding.featureImage.text.toString())
metaData.put("title", binding.title.text.toString())
metaData.put("feature_image", binding.featureImage.text.toString())
if (binding.author.text.toString() != MainActivity.credManager.username) {
var apiHost: String? = null
if (MainActivity.metaData.get("url") !== null) {
val url = Uri.parse(MainActivity.metaData.get("url"))
if (metaData.get("url") !== null) {
val url = Uri.parse(metaData.get("url"))
apiHost = url.scheme + "://" + url.host
} 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
finish()
}

@ -5,11 +5,15 @@ import android.content.Context
import android.net.Uri
import android.provider.OpenableColumns
import android.util.Log
import android.widget.Toast
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
var thisFileUri: Uri? = null
var metaData = mdMeta()
fun checkURIResource(context: Context, uri: Uri?): Boolean {
val cursor = context.contentResolver.query(uri!!, null, null, null, null)
val doesExist = cursor != null && cursor.moveToFirst()
@ -28,11 +32,55 @@ fun createFileFromStream(ins: InputStream, destination: File?) {
os.flush()
}
} catch (ex: java.lang.Exception) {
Log.e("Save File", ex.message!!)
Log.e("Save img tmp file", ex.message!!)
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) {
try {
val dir = File(context.cacheDir, "html")
@ -112,3 +160,28 @@ fun queryName(context: Context, uri: Uri): String? {
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() {
if (!easyMDE.codemirror.doc.isClean()) Android.triggerSaveFile(easyMDE.value())
easyMDE.codemirror.doc.markClean()
if (!easyMDE.codemirror.doc.isClean()) Android.triggerSaveFile()
}
function dispatchCut() {
@ -22,10 +13,7 @@ function myPreview() {
saveFile()
easyMDE.togglePreview()
}
function refresh() {
Android.refresh()
onRead()
}
function displayName() {
if (typeof Android !== 'undefined') return Android.triggerDisplayName()
else return "NONdroid"

Loading…
Cancel
Save