Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for scroll position sync #2404

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions app/src/main/assets/scroll-sync/scroll-sync.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* Scroll to the target element by source line number (generally the first visible line number).
* Params: lineNumber - the line number of source code in the editor.
*/
function edit2Preview(lineNumber) {
let increment = 0;
let direction = 0;
let number = lineNumber;
while (number > 0) {
if (direction > 0) {
number = lineNumber + increment;
direction = -1;
} else if (direction < 0) {
number = lineNumber - increment;
direction = 1;
increment++;
} else {
direction = 1;
increment++;
}

const elements = document.querySelectorAll("[data-line='" + number + "']");
if (elements == null) {
continue;
}

let completed = false;
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
if (element.offsetHeight > 0) {
element.scrollIntoView();
completed = true;
break;
}
}

if (completed) {
break;
}
}
}

/**
* Find the target line number, that is the value of data-line attribute of target element (generally the first visible element).
* Return: -1 if the target element cannot not be found.
*/
function preview2Edit() {
const elements = document.querySelectorAll("[data-line]");
if (elements == null) {
return -1;
}

const TOP_MARGIN = -20;
const BOTTOM_MARGIN = window.innerHeight;
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
const top = element.getBoundingClientRect().top;
const bottom = element.getBoundingClientRect().bottom;
if (top > TOP_MARGIN && bottom > 0 && bottom < BOTTOM_MARGIN) {
return parseInt(element.getAttribute("data-line"));
}
}

return -1;
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,17 @@
import net.gsantner.opoc.util.GsContextUtils;
import net.gsantner.opoc.util.GsCoolExperimentalStuff;
import net.gsantner.opoc.web.GsWebViewChromeClient;
import net.gsantner.opoc.web.GsWebViewClient;
import net.gsantner.opoc.wrapper.GsTextWatcherAdapter;

import java.io.File;
import java.util.Collections;

@SuppressWarnings({"UnusedReturnValue"})
@SuppressLint("NonConstantResourceId")
public class DocumentEditAndViewFragment extends MarkorBaseFragment implements FormatRegistry.TextFormatApplier {
public static final String FRAGMENT_TAG = "DocumentEditAndViewFragment";
public static final String SAVESTATE_DOCUMENT = "DOCUMENT";
public static final String SAVE_STATE_DOCUMENT = "DOCUMENT";
public static final String START_PREVIEW = "START_PREVIEW";

public static DocumentEditAndViewFragment newInstance(final @NonNull Document document, final Integer lineNumber, final Boolean preview) {
Expand Down Expand Up @@ -103,6 +105,7 @@ public static DocumentEditAndViewFragment newInstance(final @NonNull Document do
private MenuItem _saveMenuItem, _undoMenuItem, _redoMenuItem;
private boolean _isPreviewVisible;
private boolean _nextConvertToPrintMode = false;
private int _firstVisibleLineNumber = 1;


public DocumentEditAndViewFragment() {
Expand All @@ -113,8 +116,8 @@ public DocumentEditAndViewFragment() {
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Bundle args = getArguments();
if (savedInstanceState != null && savedInstanceState.containsKey(SAVESTATE_DOCUMENT)) {
_document = (Document) savedInstanceState.getSerializable(SAVESTATE_DOCUMENT);
if (savedInstanceState != null && savedInstanceState.containsKey(SAVE_STATE_DOCUMENT)) {
_document = (Document) savedInstanceState.getSerializable(SAVE_STATE_DOCUMENT);
} else if (args != null && args.containsKey(Document.EXTRA_DOCUMENT)) {
_document = (Document) args.get(Document.EXTRA_DOCUMENT);
}
Expand Down Expand Up @@ -153,6 +156,16 @@ public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
}

_webViewClient = new MarkorWebViewClient(_webView, activity);
_webViewClient.setOnPageFinishedListener(new GsWebViewClient.OnPageFinishedListener() {
@Override
public void onPageFinished(WebView v) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
return;
}
_firstVisibleLineNumber = _hlEditor.getFirstVisibleLineNumber();
_webView.evaluateJavascript("edit2Preview(" + _firstVisibleLineNumber + ");", null);
}
});
_webView.setWebChromeClient(new GsWebViewChromeClient(_webView, activity, view.findViewById(R.id.document__fragment_fullscreen_overlay)));
_webView.setWebViewClient(_webViewClient);
_webView.addJavascriptInterface(this, "Android");
Expand Down Expand Up @@ -295,7 +308,7 @@ public void onPause() {

@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
outState.putSerializable(SAVESTATE_DOCUMENT, _document);
outState.putSerializable(SAVE_STATE_DOCUMENT, _document);
super.onSaveInstanceState(outState);
}

Expand Down Expand Up @@ -861,17 +874,25 @@ public void setViewModeVisibility(boolean show, final boolean animate) {
if (show) {
updateViewModeText();
_cu.showSoftKeyboard(activity, false, _hlEditor);
_hlEditor.clearFocus();
_hlEditor.postDelayed(() -> _cu.showSoftKeyboard(activity, false, _hlEditor), 300);
_webView.requestFocus();
GsContextUtils.fadeInOut(_webView, _primaryScrollView, animate);
} else {
_webViewClient.setRestoreScrollY(_webView.getScrollY());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
_webView.evaluateJavascript("preview2Edit();", result -> {
if (Character.isDigit(result.charAt(0))) {
final int lineNumber = Integer.parseInt(result);
if (lineNumber > 0 && (lineNumber < _firstVisibleLineNumber - 2 || lineNumber > _firstVisibleLineNumber + 2)) {
TextViewUtils.selectLines(_hlEditor, false, Collections.singletonList(lineNumber));
}
}
});
}
_hlEditor.requestFocus();
GsContextUtils.fadeInOut(_primaryScrollView, _webView, animate);
}

_nextConvertToPrintMode = false;
_isPreviewVisible = show;

_nextConvertToPrintMode = false;
((AppCompatActivity) activity).supportInvalidateOptionsMenu();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1007,16 +1007,21 @@ public static void selectWholeLines(final @Nullable Spannable text) {

public void runJumpBottomTopAction(ActionItem.DisplayMode displayMode) {
if (displayMode == ActionItem.DisplayMode.EDIT) {
int pos = _hlEditor.getSelectionStart();
_hlEditor.setSelection(pos == 0 ? _hlEditor.getText().length() : 0);
final int pos = _hlEditor.getSelectionStart();
if (pos < 1) {
_hlEditor.setSelection(_hlEditor.getText().length());
} else if (pos == _hlEditor.getText().length()) {
_hlEditor.setSelection(0);
} else {
TextViewUtils.showSelection(_hlEditor);
}
} else if (displayMode == ActionItem.DisplayMode.VIEW) {
boolean top = _webView.getScrollY() > 100;
final boolean top = _webView.getScrollY() > 100;
_webView.scrollTo(0, top ? 0 : _webView.getContentHeight());
if (!top) {
_webView.scrollBy(0, 1000);
_webView.scrollBy(0, 1000);
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import android.content.Context;
import android.net.Uri;
import android.text.format.DateFormat;
import android.util.Log;
import android.webkit.WebView;

import androidx.annotation.NonNull;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,9 @@ public String convertMarkup(String markup, Context context, boolean lightMode, b
onLoadJs += "enableLineNumbers(); adjustLineNumbers();";
}

// For scroll sync
head += JS_PREFIX + "scroll-sync/scroll-sync.js" + JS_POSTFIX;

// Deliver result
return putContentIntoTemplate(context, converted, lightMode, file, onLoadJs, head);
}
Expand Down Expand Up @@ -386,15 +389,15 @@ private String getViewHlPrismIncludes(final String theme, final boolean isLineNu
sb.append(CSS_PREFIX + "prism/plugins/toolbar/prism-toolbar.css" + CSS_POSTFIX);

sb.append(JS_PREFIX + "prism/prism.js" + JS_POSTFIX);
sb.append(JS_PREFIX + "prism/main.js" + JS_POSTFIX);
sb.append(JS_PREFIX + "prism/util.js" + JS_POSTFIX);
sb.append(JS_PREFIX + "prism/plugins/autoloader/prism-autoloader.min.js" + JS_POSTFIX);
sb.append(JS_PREFIX + "prism/plugins/toolbar/prism-toolbar.min.js" + JS_POSTFIX);
sb.append(JS_PREFIX + "prism/plugins/copy-to-clipboard/prism-copy-to-clipboard.js" + JS_POSTFIX);

if (isLineNumbersEnabled) {
sb.append(CSS_PREFIX + "prism/plugins/line-numbers/style.css" + CSS_POSTFIX);
sb.append(JS_PREFIX + "prism/plugins/line-numbers/prism-line-numbers.min.js" + JS_POSTFIX);
sb.append(JS_PREFIX + "prism/plugins/line-numbers/main.js" + JS_POSTFIX);
sb.append(JS_PREFIX + "prism/plugins/line-numbers/util.js" + JS_POSTFIX);
}

return sb.toString();
Expand Down Expand Up @@ -457,8 +460,8 @@ private static class LineNumberIdProvider implements AttributeProvider {
@Override
public void setAttributes(Node node, AttributablePart part, Attributes attributes) {
final Document document = node.getDocument();
final int lineNumber = document.getLineNumber(node.getStartOffset());
attributes.addValue("line", "" + lineNumber);
final int lineNumber = document.getLineNumber(node.getStartOffset()) + 1;
attributes.addValue("data-line", "" + lineNumber);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
Expand Down Expand Up @@ -83,9 +82,7 @@ private void computeThumbHeight() {
protected void onAttachedToWindow() {
super.onAttachedToWindow();
setSmoothScrollingEnabled(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
_ltr = getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
}
_ltr = getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
final DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
_grabWidth = (int) (1.5 * (float) getVerticalScrollbarWidth() * displayMetrics.density);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@

import net.gsantner.markor.ApplicationObject;
import net.gsantner.markor.R;
import net.gsantner.markor.format.markdown.MarkdownTextConverter;
import net.gsantner.markor.format.todotxt.TodoTxtBasicSyntaxHighlighter;
import net.gsantner.markor.format.todotxt.TodoTxtFilter;
import net.gsantner.markor.format.todotxt.TodoTxtTask;
Expand Down Expand Up @@ -842,14 +841,19 @@ public static void showHeadlineDialog(

dopt.positionCallback = result -> {
final int index = filtered.get(result.get(0));
final int line = headings.get(index).line;

TextViewUtils.selectLines(edit, line);
final String jumpJs = "document.querySelector('[line=\"" + line + "\"]').scrollIntoView();";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
webView.evaluateJavascript(jumpJs, null);
} else {
webView.loadUrl("javascript:" + jumpJs);
final int line = headings.get(index).line + 1;

if (edit.isShown()) {
TextViewUtils.selectLines(edit, false, Collections.singletonList(line));
}

if (webView.isShown()) {
final String jumpJs = "document.querySelector(\"[data-line='" + line + "']\").scrollIntoView();";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
webView.evaluateJavascript(jumpJs, null);
} else {
webView.loadUrl("javascript:" + jumpJs);
}
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,32 @@ private int rowEnd(final int y) {
return layout.getLineEnd(line);
}

public int getFirstVisibleLineNumber() {
final Rect visibleRect = new Rect();
if (!getLocalVisibleRect(visibleRect)) {
return -1;
}

final CharSequence text = getText();
final Layout layout = getLayout();
if (text == null || layout == null) {
return -1;
}

// Calculate the first visible line number
final int count = layout.getLineCount();
for (int i = 1, number = 1; i < count; i++) {
if (text.charAt(layout.getLineStart(i) - 1) == '\n') {
if (layout.getLineTop(i) > visibleRect.top) {
return number;
}
number++;
}
}

return 1;
}

// Various overrides
// ---------------------------------------------------------------------------------------------
public void setSaveInstanceState(final boolean save) {
Expand Down
Loading
Loading