Mainly layouts and refacts
parent
90bc2ddc2e
commit
1bc38faf5a
@ -0,0 +1,45 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using Android.App;
|
||||||
|
using Android.Content;
|
||||||
|
using Android.OS;
|
||||||
|
using Android.Runtime;
|
||||||
|
using Android.Views;
|
||||||
|
using Android.Widget;
|
||||||
|
using Android.Webkit;
|
||||||
|
using Java.Interop;
|
||||||
|
|
||||||
|
namespace BookAStar.Droid.Markdown
|
||||||
|
{
|
||||||
|
public class JsBridgeMarkdown : Java.Lang.Object
|
||||||
|
{
|
||||||
|
readonly WeakReference<MarkdownViewRenderer> hybridWebViewRenderer;
|
||||||
|
|
||||||
|
public JsBridgeMarkdown(MarkdownViewRenderer hybridRenderer)
|
||||||
|
{
|
||||||
|
hybridWebViewRenderer = new WeakReference<MarkdownViewRenderer>(hybridRenderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
[JavascriptInterface]
|
||||||
|
[Export("contentEdited")]
|
||||||
|
public void ContentEdited(string data)
|
||||||
|
{
|
||||||
|
MarkdownViewRenderer hybridRenderer;
|
||||||
|
|
||||||
|
if (hybridWebViewRenderer != null && hybridWebViewRenderer.TryGetTarget(out hybridRenderer))
|
||||||
|
{
|
||||||
|
hybridRenderer.Element.Markdown = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[JavascriptInterface]
|
||||||
|
[Export("jsLoaded")]
|
||||||
|
public void JSLoaded()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,96 @@
|
|||||||
|
@model MarkdownViewModel
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<style>
|
||||||
|
.standalone-container {
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@if (Model.Editable)
|
||||||
|
{
|
||||||
|
<link rel="stylesheet" href="quill.snow.css" />
|
||||||
|
<style>
|
||||||
|
#bubble-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bubble-container div.ql-editor {
|
||||||
|
padding-top:3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="standalone-container">
|
||||||
|
<div id="bubble-container">@Html.Write(Model.GetHtml())</div>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript" src="jquery.js"></script>
|
||||||
|
@if (Model.Editable)
|
||||||
|
{
|
||||||
|
<script type="text/javascript" src="quill.min.js"></script>
|
||||||
|
<script type="text/javascript" src="showdown.js"></script>
|
||||||
|
<script type="text/javascript" src="to-markdown.js"></script>
|
||||||
|
<script type="text/javascript" src="md-helpers.js"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
var toolbarOptions = [
|
||||||
|
['bold', 'italic', 'underline', 'strike'], // toggled buttons
|
||||||
|
['blockquote', 'code-block'],
|
||||||
|
[{ 'header': 1 }, { 'header': 2 }, { 'header': 3 }], // custom button values
|
||||||
|
[{ 'list': 'ordered' }, { 'list': 'bullet' }],
|
||||||
|
[{ 'indent': '-1' }, { 'indent': '+1' }], // outdent/indent
|
||||||
|
['link', 'image', 'video'],
|
||||||
|
['clean'] // remove formatting button
|
||||||
|
];
|
||||||
|
|
||||||
|
var showImageUI = function (value) {
|
||||||
|
if (value) {
|
||||||
|
var href = prompt('Enter the URL');
|
||||||
|
this.quill.format('image', href);
|
||||||
|
} else {
|
||||||
|
this.quill.format('image', false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
var quill = new Quill('#bubble-container', {
|
||||||
|
modules: {
|
||||||
|
toolbar: toolbarOptions
|
||||||
|
},
|
||||||
|
placeholder: 'Composez votre texte ...',
|
||||||
|
theme: 'snow'
|
||||||
|
});
|
||||||
|
|
||||||
|
function getMD() {
|
||||||
|
return markdownize($('#bubble-container div.ql-editor').html())
|
||||||
|
}
|
||||||
|
quill.on('text-change', function (delta, oldDelta, source) {
|
||||||
|
if (source === "user") {
|
||||||
|
contentEdited(getMD());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var toolbar = quill.getModule('toolbar');
|
||||||
|
toolbar.addHandler('image', showImageUI);
|
||||||
|
jsLoaded();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function() {
|
||||||
|
jsLoaded();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using Android.App;
|
||||||
|
using Android.Content;
|
||||||
|
using Android.OS;
|
||||||
|
using Android.Runtime;
|
||||||
|
using Android.Views;
|
||||||
|
using Android.Widget;
|
||||||
|
|
||||||
|
namespace BookAStar.Droid.Markdown
|
||||||
|
{
|
||||||
|
public class MarkdownViewModel
|
||||||
|
{
|
||||||
|
protected static MarkdownDeep.Markdown markdown = new MarkdownDeep.Markdown();
|
||||||
|
public string Content { get; set; }
|
||||||
|
public bool Editable { get; set; }
|
||||||
|
public string GetHtml()
|
||||||
|
{
|
||||||
|
return markdown.Transform(Content);
|
||||||
|
}
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,100 @@
|
|||||||
|
|
||||||
|
using BookAStar.Views;
|
||||||
|
using Android.Webkit;
|
||||||
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
using BookAStar.Droid;
|
||||||
|
using System;
|
||||||
|
using Java.Interop;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using Android.Views;
|
||||||
|
|
||||||
|
[assembly: Xamarin.Forms.ExportRenderer(typeof(MarkdownView), typeof(MarkdownViewRenderer))]
|
||||||
|
namespace BookAStar.Droid
|
||||||
|
{
|
||||||
|
using Markdown;
|
||||||
|
public class MarkdownViewRenderer : ViewRenderer<MarkdownView, WebView>
|
||||||
|
{
|
||||||
|
private WebView editorView;
|
||||||
|
private MarkdownEditor editorTemplate = new MarkdownEditor();
|
||||||
|
const string jsLoadedJavaScriptFunction = "function jsLoaded(){jsBridge.jsLoaded()}";
|
||||||
|
const string contentEditedJavaScriptFunction = "function contentEdited(data){jsBridge.contentEdited(data)}";
|
||||||
|
public WebView EditorView
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return editorView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// To be called once document finished loading
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="xview"></param>
|
||||||
|
/// <param name="view"></param>
|
||||||
|
public static async void AdjustHeightRequest(MarkdownView xview, WebView view)
|
||||||
|
{
|
||||||
|
if (view == null || xview == null) return;
|
||||||
|
var vch = view.ContentHeight;
|
||||||
|
xview.HeightRequest = vch > xview.MinimumHeightRequest ? vch : xview.MinimumHeightRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnElementChanged(ElementChangedEventArgs<MarkdownView> e)
|
||||||
|
{
|
||||||
|
base.OnElementChanged(e);
|
||||||
|
if (Control == null)
|
||||||
|
{
|
||||||
|
SetNativeControl(CreateNativeControl());
|
||||||
|
InjectJS(jsLoadedJavaScriptFunction);
|
||||||
|
InjectJS(contentEditedJavaScriptFunction);
|
||||||
|
}
|
||||||
|
if (e.OldElement != null)
|
||||||
|
{
|
||||||
|
// Unsubscribe
|
||||||
|
}
|
||||||
|
if (e.NewElement != null)
|
||||||
|
{
|
||||||
|
// Subscribe
|
||||||
|
editorTemplate.Model = new Markdown.MarkdownViewModel
|
||||||
|
{ Content = e.NewElement.Markdown, Editable = e.NewElement.Editable };
|
||||||
|
var html = editorTemplate.GenerateString();
|
||||||
|
EditorView.LoadDataWithBaseURL("file:///android_asset/",
|
||||||
|
html, "text/html", "utf-8", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InjectJS(string script)
|
||||||
|
{
|
||||||
|
if (Control != null)
|
||||||
|
{
|
||||||
|
Control.LoadUrl(string.Format("javascript: {0}", script));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private WebView CreateNativeControl()
|
||||||
|
{
|
||||||
|
editorView = new WebView(Context);
|
||||||
|
EditorView.SetWebChromeClient(
|
||||||
|
new MarkdownWebChromeClient()
|
||||||
|
);
|
||||||
|
EditorView.Settings.BuiltInZoomControls = false;
|
||||||
|
EditorView.Settings.JavaScriptEnabled = true;
|
||||||
|
EditorView.Settings.LoadsImagesAutomatically = true;
|
||||||
|
EditorView.Settings.SetAppCacheEnabled(true);
|
||||||
|
EditorView.Settings.AllowContentAccess = true;
|
||||||
|
EditorView.Settings.AllowFileAccess = true;
|
||||||
|
EditorView.Settings.AllowFileAccessFromFileURLs = true;
|
||||||
|
EditorView.Settings.AllowUniversalAccessFromFileURLs = true;
|
||||||
|
EditorView.Settings.BlockNetworkImage = false;
|
||||||
|
EditorView.Settings.BlockNetworkLoads = false;
|
||||||
|
EditorView.Settings.DomStorageEnabled = true;
|
||||||
|
EditorView.AddJavascriptInterface(new JsBridgeMarkdown(this), "jsBridge");
|
||||||
|
EditorView.ViewTreeObserver.PreDraw += ViewTreeObserver_PreDraw;
|
||||||
|
return EditorView;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ViewTreeObserver_PreDraw(object sender, ViewTreeObserver.PreDrawEventArgs e)
|
||||||
|
{
|
||||||
|
AdjustHeightRequest(Element, Control);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using Android.App;
|
||||||
|
using Android.Content;
|
||||||
|
using Android.OS;
|
||||||
|
using Android.Runtime;
|
||||||
|
using Android.Views;
|
||||||
|
using Android.Widget;
|
||||||
|
using Android.Webkit;
|
||||||
|
|
||||||
|
namespace BookAStar.Droid.Markdown
|
||||||
|
{
|
||||||
|
class MarkdownWebChromeClient : WebChromeClient
|
||||||
|
{
|
||||||
|
/*public override void OnConsoleMessage(string message, int lineNumber, string sourceID)
|
||||||
|
{
|
||||||
|
base.OnConsoleMessage(message, lineNumber, sourceID);
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,146 +0,0 @@
|
|||||||
|
|
||||||
using BookAStar.Views;
|
|
||||||
using Android.Webkit;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
using BookAStar.Droid;
|
|
||||||
using System;
|
|
||||||
using Java.Interop;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using Android.Views;
|
|
||||||
|
|
||||||
[assembly: Xamarin.Forms.ExportRenderer(typeof(MarkdownView), typeof(MarkdownViewRenderer))]
|
|
||||||
namespace BookAStar.Droid
|
|
||||||
{
|
|
||||||
public class JsBridgeMarkdown : Java.Lang.Object
|
|
||||||
{
|
|
||||||
readonly WeakReference<MarkdownViewRenderer> hybridWebViewRenderer;
|
|
||||||
|
|
||||||
public JsBridgeMarkdown(MarkdownViewRenderer hybridRenderer)
|
|
||||||
{
|
|
||||||
hybridWebViewRenderer = new WeakReference<MarkdownViewRenderer>(hybridRenderer);
|
|
||||||
}
|
|
||||||
|
|
||||||
[JavascriptInterface]
|
|
||||||
[Export("invokeAction")]
|
|
||||||
public void InvokeAction(string data)
|
|
||||||
{
|
|
||||||
MarkdownViewRenderer hybridRenderer;
|
|
||||||
|
|
||||||
if (hybridWebViewRenderer != null && hybridWebViewRenderer.TryGetTarget(out hybridRenderer))
|
|
||||||
{
|
|
||||||
hybridRenderer.Element.Markdown = data;
|
|
||||||
MarkdownViewRenderer.AdjustHeightRequest(hybridRenderer.Element,
|
|
||||||
hybridRenderer.EditorView);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MarkdownViewRenderer : ViewRenderer<MarkdownView, WebView>
|
|
||||||
{
|
|
||||||
private WebView editorView;
|
|
||||||
private MarkdownEditor editorTemplate = new MarkdownEditor();
|
|
||||||
private MarkdownDeep.Markdown markdown = new MarkdownDeep.Markdown();
|
|
||||||
const string JavaScriptFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
|
|
||||||
|
|
||||||
public WebView EditorView
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return editorView;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// To be called once document finished loading
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="xview"></param>
|
|
||||||
/// <param name="view"></param>
|
|
||||||
public static async void AdjustHeightRequest(MarkdownView xview, WebView view)
|
|
||||||
{
|
|
||||||
|
|
||||||
xview.BatchBegin();
|
|
||||||
var vch = view.ContentHeight; // FIXME why not 3?
|
|
||||||
xview.HeightRequest = vch > xview.MinimumHeightRequest ? vch : xview.MinimumHeightRequest;
|
|
||||||
xview.BatchCommit();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetMDEditorText(string text)
|
|
||||||
{
|
|
||||||
editorTemplate.Model = (text == null) ? null : markdown.Transform(text);
|
|
||||||
var html = editorTemplate.GenerateString();
|
|
||||||
EditorView.LoadDataWithBaseURL("file:///android_asset/",
|
|
||||||
html, "text/html", "utf-8", null);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnElementChanged(ElementChangedEventArgs<MarkdownView> e)
|
|
||||||
{
|
|
||||||
base.OnElementChanged(e);
|
|
||||||
if (Control == null)
|
|
||||||
{
|
|
||||||
SetNativeControl(CreateNativeControl());
|
|
||||||
InjectJS(JavaScriptFunction);
|
|
||||||
}
|
|
||||||
if (e.OldElement != null)
|
|
||||||
{
|
|
||||||
// Unsubscribe
|
|
||||||
}
|
|
||||||
if (e.NewElement != null)
|
|
||||||
{
|
|
||||||
// Subscribe
|
|
||||||
var viewclient = new MarkdownWebViewClient(
|
|
||||||
md => { e.NewElement.Markdown = md; });
|
|
||||||
EditorView.SetWebViewClient(viewclient);
|
|
||||||
Control.AddJavascriptInterface(new JsBridgeMarkdown(this), "jsBridge");
|
|
||||||
SetMDEditorText(e.NewElement.Markdown);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void InjectJS(string script)
|
|
||||||
{
|
|
||||||
if (Control != null)
|
|
||||||
{
|
|
||||||
Control.LoadUrl(string.Format("javascript: {0}", script));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private WebView CreateNativeControl()
|
|
||||||
{
|
|
||||||
editorView = new WebView(Context);
|
|
||||||
EditorView.Settings.BuiltInZoomControls = false;
|
|
||||||
EditorView.Settings.JavaScriptEnabled = true;
|
|
||||||
EditorView.Settings.LoadsImagesAutomatically = true;
|
|
||||||
EditorView.Settings.SetAppCacheEnabled(true);
|
|
||||||
EditorView.Settings.AllowContentAccess = true;
|
|
||||||
EditorView.Settings.AllowFileAccess = true;
|
|
||||||
EditorView.Settings.AllowFileAccessFromFileURLs = true;
|
|
||||||
EditorView.Settings.AllowUniversalAccessFromFileURLs = true;
|
|
||||||
EditorView.Settings.BlockNetworkImage = false;
|
|
||||||
EditorView.Settings.BlockNetworkLoads = false;
|
|
||||||
EditorView.Settings.DomStorageEnabled = true;
|
|
||||||
|
|
||||||
// editorView.SetMinimumHeight(300);
|
|
||||||
return EditorView;
|
|
||||||
}
|
|
||||||
// FIXME no impact...
|
|
||||||
protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
|
||||||
{
|
|
||||||
MeasureSpecMode widthMode = MeasureSpec.GetMode(widthMeasureSpec);
|
|
||||||
MeasureSpecMode heightMode = MeasureSpec.GetMode(heightMeasureSpec);
|
|
||||||
int widthSize = MeasureSpec.GetSize(widthMeasureSpec);
|
|
||||||
int heightSize = MeasureSpec.GetSize(heightMeasureSpec);
|
|
||||||
int pxHeight = (int)ContextExtensions.ToPixels(Context, Element.HeightRequest);
|
|
||||||
int pxWidth = (int)ContextExtensions.ToPixels(Context, Element.WidthRequest);
|
|
||||||
var measuredWidth = widthMode != MeasureSpecMode.Exactly ? (widthMode != MeasureSpecMode.AtMost ? pxHeight : Math.Min(pxHeight, widthSize)) : widthSize;
|
|
||||||
var measuredHeight = heightMode != MeasureSpecMode.Exactly ? (heightMode != MeasureSpecMode.AtMost ? pxWidth : Math.Min(pxWidth, heightSize)) : heightSize;
|
|
||||||
SetMeasuredDimension(measuredWidth, measuredHeight< Element.HeightRequest ? (int) Element.HeightRequest : measuredHeight);
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
protected override void OnLayout(bool changed, int left, int top, int right, int bottom)
|
|
||||||
{
|
|
||||||
Element.Layout(new Xamarin.Forms.Rectangle(0, 0, ContextExtensions.FromPixels(Context, right - left), ContextExtensions.FromPixels(Context, bottom - top)));
|
|
||||||
base.OnLayout(changed, left, top, right, bottom);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Android.App;
|
|
||||||
using Android.Webkit;
|
|
||||||
|
|
||||||
namespace BookAStar.Droid
|
|
||||||
{
|
|
||||||
|
|
||||||
class MarkdownWebViewClient : WebViewClient
|
|
||||||
{
|
|
||||||
Action<string> update;
|
|
||||||
public MarkdownWebViewClient(Action<string> update) : base()
|
|
||||||
{
|
|
||||||
this.update = update;
|
|
||||||
}
|
|
||||||
private static Activity getActivity ()
|
|
||||||
{
|
|
||||||
return (Activity)App.PlatformSpecificInstance;
|
|
||||||
}
|
|
||||||
public string Markdown { get; private set; }
|
|
||||||
public override WebResourceResponse ShouldInterceptRequest(WebView view, IWebResourceRequest request)
|
|
||||||
{
|
|
||||||
if (request.Url.Scheme=="file")
|
|
||||||
{
|
|
||||||
if (request.Url.Path=="/android_asset/validate")
|
|
||||||
{
|
|
||||||
// TODO Better,
|
|
||||||
// by inspecting the form entries from the view
|
|
||||||
Markdown = request.Url.GetQueryParameter("md");
|
|
||||||
update(Markdown);
|
|
||||||
return new WebResourceResponse("application/json", "utf-8" ,200, "Ok", null, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return base.ShouldInterceptRequest(view, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue