Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
454fdf6
add support for scroll sync
guanglinn Sep 3, 2024
365714c
minor improvements
guanglinn Sep 3, 2024
6f50f40
adjust margin to find target elements and other improvements
guanglinn Sep 4, 2024
b03e0b8
enhance behavior on tool bar long clicked
guanglinn Sep 4, 2024
7ec4056
minor improvements
guanglinn Sep 4, 2024
6021a83
improve method of finding first visible line number
guanglinn Sep 5, 2024
54bd600
minor improvements
guanglinn Sep 5, 2024
e6c4ed2
Merge branch 'master' into scroll_sync
guanglinn Sep 17, 2024
0b2439e
Merge branch 'master' into scroll_sync
gsantner Sep 24, 2024
7d3794f
Merge branch 'master' into scroll_sync
gsantner Sep 24, 2024
2c534c2
Merge branch 'master' into scroll_sync
guanglinn Sep 25, 2024
9193e68
minor improvements
guanglinn Sep 25, 2024
cc34cff
long click toolbar to restore last view position
guanglinn Sep 26, 2024
4ff07de
Merge branch 'master' into scroll_sync
guanglinn Nov 6, 2024
4ae51f4
Merge branch 'master' into scroll_sync
guanglinn Nov 16, 2024
3f42e2b
Merge branch 'master' into scroll_sync
guanglinn Dec 13, 2024
c53691b
remove boolean flags from selection setting methods
guanglinn Dec 15, 2024
adaab2f
Merge branch 'scroll_sync' of https://github.com/guanglinn/markor int…
guanglinn Dec 15, 2024
c4d42e0
Merge branch 'master' into scroll_sync
guanglinn Dec 24, 2024
1c1a42f
resolve incorrect merging
guanglinn Dec 24, 2024
cbc6cf4
Merge branch 'master' into scroll_sync
guanglinn Jan 3, 2025
a310486
Merge branch 'master' into scroll_sync
guanglinn Jan 31, 2025
f8bb756
Merge branch 'master' into scroll_sync
guanglinn Sep 21, 2025
af49bc4
resolve conflicts
guanglinn Sep 21, 2025
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 @@ -37,6 +37,7 @@
import net.gsantner.opoc.util.GsFileUtils;

import java.io.File;
import java.util.List;

import other.so.AndroidBug5497Workaround;

Expand Down Expand Up @@ -213,7 +214,7 @@ private void handleLaunchingIntent(final Intent intent) {
if (editFrag.getDocument().path.equals(doc.path)) {
if (startLine != null) {
// Same document requested, show the requested line
TextViewUtils.selectLines(editFrag.getEditor(), startLine);
TextViewUtils.selectLines(editFrag.getEditor(), List.of(startLine));
}
} else {
// Current document is different - launch the new document
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
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;
Expand All @@ -73,7 +74,7 @@
@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 float VIEW_FONT_SCALE = 100f / 15.7f;
Expand Down Expand Up @@ -109,6 +110,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() {
super();
Expand All @@ -118,8 +120,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 @@ -164,6 +166,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 @@ -305,6 +317,14 @@ public void onPause() {
_appSettings.setDocumentPreviewState(_document.path, _isPreviewVisible);
_appSettings.setLastEditPosition(_document.path, TextViewUtils.getSelection(_hlEditor)[0]);

int y;
if (_webView.isShown()) {
y = _webView.getScrollY();
} else {
y = _webViewClient.getRestoreScrollY();
}
_appSettings.setLastViewPositionY(_document.path, y);

if (_document.path.equals(_appSettings.getTodoFile().getAbsolutePath())) {
TodoWidgetProvider.updateTodoWidgets();
}
Expand All @@ -313,7 +333,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 @@ -889,17 +909,26 @@ 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, _verticalScrollView, 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.jumpToLine(_hlEditor, lineNumber);
}
}
});
}
_hlEditor.requestFocus();
GsContextUtils.fadeInOut(_verticalScrollView, _webView, animate);
}

_nextConvertToPrintMode = false;
_isPreviewVisible = show;

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

Expand All @@ -924,7 +953,15 @@ protected void onToolbarClicked(View v) {
@Override
protected boolean onToolbarLongClicked(View v) {
if (isVisible() && isResumed()) {
_format.getActions().runJumpBottomTopAction(_isPreviewVisible ? ActionButtonBase.ActionItem.DisplayMode.VIEW : ActionButtonBase.ActionItem.DisplayMode.EDIT);
if (_isPreviewVisible) {
if (_webViewClient.getRestoreScrollY() < 1) {
final int y = _appSettings.getLastViewPositionY(_document.path, 1);
_webViewClient.setRestoreScrollY(y);
}
_webViewClient.restoreScrollY(_webView);
} else {
_format.getActions().runJumpBottomTopAction(ActionButtonBase.ActionItem.DisplayMode.EDIT);
}
return true;
}
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1010,10 +1010,16 @@ 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);
Expand Down
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 @@ -352,6 +352,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 @@ -465,8 +468,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 @@ -816,10 +816,19 @@ public static void showHeadlineDialog(
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);
if (edit.isShown()) {
final List<Integer> positions = Collections.singletonList(line);
TextViewUtils.selectLines(edit, positions);
TextViewUtils.selectLines(edit, positions);
}

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 @@ -290,6 +290,32 @@ private int rowEnd(final int y) {
return layout == null ? 0 : layout.getLineEnd(layout.getLineForVertical(y));
}

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;
}

// Additional selections for search / replace etc
// ---------------------------------------------------------------------------------------------

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,11 +264,6 @@ public static int getIndexFromLineOffset(final CharSequence s, final int l, fina
return i;
}


public static void selectLines(final EditText edit, final Integer... positions) {
selectLines(edit, Arrays.asList(positions));
}

/**
* Select the given indices.
* Case 1: Only one index -> Put cursor on that line
Expand Down Expand Up @@ -314,12 +309,7 @@ public static void selectLines(final EditText edit, final List<Integer> position
}
}

public static void showSelection(final TextView text) {
showSelection(text, text.getSelectionStart(), text.getSelectionEnd());
}

public static void showSelection(final TextView text, final int start, final int end) {

// Get view info
// ------------------------------------------------------------
final Layout layout = text.getLayout();
Expand Down Expand Up @@ -363,6 +353,10 @@ public static void showSelection(final TextView text, final int start, final int
text.requestRectangleOnScreen(region, true);
}

public static void showSelection(final TextView text) {
showSelection(text, text.getSelectionStart(), text.getSelectionEnd());
}

public static void setSelectionAndShow(final EditText edit, final int... sel) {
if (sel == null || sel.length == 0) {
return;
Expand All @@ -375,12 +369,18 @@ public static void setSelectionAndShow(final EditText edit, final int... sel) {
if (!edit.hasFocus() && edit.getVisibility() != View.GONE) {
edit.requestFocus();
}

edit.setSelection(start, end);
showSelection(edit, start, end);
}
}

public static void jumpToLine(final EditText edit, final int number) {
final int selection = TextViewUtils.getIndexFromLineOffset(edit.getText(), number, 0);
if (GsTextUtils.inRange(0, edit.length(), selection, selection)) {
showSelection(edit, selection, selection);
}
}

/**
* Snippets are evaluated in the following order:
* 1. {{*}} style placeholders are replaced (except {{cursor}})
Expand Down
Loading