mirror of https://github.com/joplin/website.git
17 lines
33 KiB
HTML
17 lines
33 KiB
HTML
<!doctype html>
|
||
<html lang="en" dir="ltr" class="docs-wrapper docs-doc-page docs-version-current plugin-docs plugin-id-default docs-doc-id-dev/spec/sync_scroll" data-has-hydrated="false">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="generator" content="Docusaurus v2.4.3">
|
||
<title data-rh="true">Synchronous Scroll | Joplin</title><meta data-rh="true" name="viewport" content="width=device-width,initial-scale=1"><meta data-rh="true" name="twitter:card" content="summary_large_image"><meta data-rh="true" property="og:url" content="https://joplinapp.org/help/dev/spec/sync_scroll"><meta data-rh="true" name="docusaurus_locale" content="en"><meta data-rh="true" name="docsearch:language" content="en"><meta data-rh="true" name="docusaurus_version" content="current"><meta data-rh="true" name="docusaurus_tag" content="docs-default-current"><meta data-rh="true" name="docsearch:version" content="current"><meta data-rh="true" name="docsearch:docusaurus_tag" content="docs-default-current"><meta data-rh="true" property="og:title" content="Synchronous Scroll | Joplin"><meta data-rh="true" name="description" content="Since Joplin 2.6, the synchronous scroll feature (Sync Scroll) for two-pane Markdown Editor is introduced. This document describes its detail. Motivation Joplin has two types of editors, two-pane M..."><meta data-rh="true" property="og:description" content="Since Joplin 2.6, the synchronous scroll feature (Sync Scroll) for two-pane Markdown Editor is introduced. This document describes its detail. Motivation Joplin has two types of editors, two-pane M..."><meta data-rh="true" property="og:image" content="https://joplinapp.org/images/sync_scroll/abst-wo-sync2.png"><meta data-rh="true" name="twitter:image" content="https://joplinapp.org/images/sync_scroll/abst-wo-sync2.png"><link data-rh="true" rel="icon" href="https://joplinapp.org/favicon.ico"><link data-rh="true" rel="canonical" href="https://joplinapp.org/help/dev/spec/sync_scroll"><link data-rh="true" rel="alternate" href="https://joplinapp.org/help/dev/spec/sync_scroll" hreflang="en"><link data-rh="true" rel="alternate" href="https://joplinapp.org/fr/help/dev/spec/sync_scroll" hreflang="fr"><link data-rh="true" rel="alternate" href="https://joplinapp.org/help/dev/spec/sync_scroll" hreflang="x-default"><link rel="alternate" type="application/rss+xml" href="/news/rss.xml" title="Joplin RSS Feed">
|
||
<link rel="alternate" type="application/atom+xml" href="/news/atom.xml" title="Joplin Atom Feed"><link rel="stylesheet" href="/assets/css/styles.3f0207fa.css">
|
||
<link rel="preload" href="/assets/js/runtime~main.1fb4f966.js" as="script">
|
||
<link rel="preload" href="/assets/js/main.aa9faae4.js" as="script">
|
||
</head>
|
||
<body class="navigation-with-keyboard">
|
||
<script>!function(){function t(t){document.documentElement.setAttribute("data-theme",t)}var e=function(){var t=null;try{t=new URLSearchParams(window.location.search).get("docusaurus-theme")}catch(t){}return t}()||function(){var t=null;try{t=localStorage.getItem("theme")}catch(t){}return t}();t(null!==e?e:"light")}()</script><div id="__docusaurus">
|
||
<div role="region" aria-label="Skip to main content"><a class="skipToContent_fXgn" href="#__docusaurus_skipToContent_fallback">Skip to main content</a></div><nav aria-label="Main" class="navbar navbar--fixed-top"><div class="navbar__inner"><div class="navbar__items"><button aria-label="Toggle navigation bar" aria-expanded="false" class="navbar__toggle clean-btn" type="button"><svg width="30" height="30" viewBox="0 0 30 30" aria-hidden="true"><path stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2" d="M4 7h22M4 15h22M4 23h22"></path></svg></button><a href="https://joplinapp.org" target="_self" rel="noopener noreferrer" class="navbar__brand"><div class="navbar__logo"><img src="/images/logo-text-blue.svg" alt="Joplin" class="themedImage_ToTc themedImage--light_HNdA"><img src="/images/logo-text-blue.svg" alt="Joplin" class="themedImage_ToTc themedImage--dark_i4oU"></div><b class="navbar__title text--truncate"></b></a></div><div class="navbar__items navbar__items--right"><a class="navbar__item navbar__link" href="/news">News</a><a aria-current="page" class="navbar__item navbar__link navbar__link--active" href="/help/">Help</a><a href="https://discourse.joplinapp.org" target="_blank" rel="noopener noreferrer" class="navbar__item navbar__link">Forum</a><a href="https://joplinapp.org/plans" target="_self" rel="noopener noreferrer" class="navbar__item navbar__link navbar-custom-buttons plans-button">Joplin Cloud</a><a href="https://joplinapp.org/donate" target="_self" rel="noopener noreferrer" class="navbar__item navbar__link navbar-custom-buttons sponsor-button">♡ Support us</a><div class="navbar__item dropdown dropdown--hoverable dropdown--right"><a href="#" aria-haspopup="true" aria-expanded="false" role="button" class="navbar__link"><svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true" class="iconLanguage_nlXk"><path fill="currentColor" d="M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z"></path></svg>English</a><ul class="dropdown__menu"><li><a href="/help/dev/spec/sync_scroll" target="_self" rel="noopener noreferrer" class="dropdown__link dropdown__link--active" lang="en">English</a></li><li><a href="/fr/help/dev/spec/sync_scroll" target="_self" rel="noopener noreferrer" class="dropdown__link" lang="fr">Français</a></li></ul></div><div class="toggle_vylO colorModeToggle_DEke"><button class="clean-btn toggleButton_gllP toggleButtonDisabled_aARS" type="button" disabled="" title="Switch between dark and light mode (currently light mode)" aria-label="Switch between dark and light mode (currently light mode)" aria-live="polite"><svg viewBox="0 0 24 24" width="24" height="24" class="lightToggleIcon_pyhR"><path fill="currentColor" d="M12,9c1.65,0,3,1.35,3,3s-1.35,3-3,3s-3-1.35-3-3S10.35,9,12,9 M12,7c-2.76,0-5,2.24-5,5s2.24,5,5,5s5-2.24,5-5 S14.76,7,12,7L12,7z M2,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S1.45,13,2,13z M20,13l2,0c0.55,0,1-0.45,1-1 s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S19.45,13,20,13z M11,2v2c0,0.55,0.45,1,1,1s1-0.45,1-1V2c0-0.55-0.45-1-1-1S11,1.45,11,2z M11,20v2c0,0.55,0.45,1,1,1s1-0.45,1-1v-2c0-0.55-0.45-1-1-1C11.45,19,11,19.45,11,20z M5.99,4.58c-0.39-0.39-1.03-0.39-1.41,0 c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0s0.39-1.03,0-1.41L5.99,4.58z M18.36,16.95 c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0c0.39-0.39,0.39-1.03,0-1.41 L18.36,16.95z M19.42,5.99c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41 s1.03,0.39,1.41,0L19.42,5.99z M7.05,18.36c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06 c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L7.05,18.36z"></path></svg><svg viewBox="0 0 24 24" width="24" height="24" class="darkToggleIcon_wfgR"><path fill="currentColor" d="M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"></path></svg></button></div><div class="searchBox_ZlJk"><div class="navbar__search"><span aria-label="expand searchbar" role="button" class="search-icon" tabindex="0"></span><input type="search" id="search_input_react" placeholder="Loading..." aria-label="Search" class="navbar__search-input search-bar" disabled=""></div></div></div></div><div role="presentation" class="navbar-sidebar__backdrop"></div></nav><div id="__docusaurus_skipToContent_fallback" class="main-wrapper mainWrapper_z2l0 docsWrapper_BCFX"><button aria-label="Scroll back to top" class="clean-btn theme-back-to-top-button backToTopButton_sjWU" type="button"></button><div class="docPage__5DB"><aside class="theme-doc-sidebar-container docSidebarContainer_b6E3"><div class="sidebarViewport_Xe31"><div class="sidebar_njMd"><nav aria-label="Docs sidebar" class="menu thin-scrollbar menu_SIkG"><ul class="theme-doc-sidebar-menu menu__list"><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-1 menu__list-item"><a class="menu__link" href="/help/">What is Joplin?</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-1 menu__list-item"><a class="menu__link" href="/help/install">Installation</a></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist" aria-expanded="false" href="/help/apps/">Using Joplin</a><button aria-label="Toggle the collapsible sidebar category 'Using Joplin'" type="button" class="clean-btn menu__caret"></button></div></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist" aria-expanded="false" href="/help/api/">Programmatic API</a><button aria-label="Toggle the collapsible sidebar category 'Programmatic API'" type="button" class="clean-btn menu__caret"></button></div></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist menu__link--active" aria-expanded="true" href="/help/dev/">Development</a><button aria-label="Toggle the collapsible sidebar category 'Development'" type="button" class="clean-btn menu__caret"></button></div><ul style="display:block;overflow:visible;height:auto" class="menu__list"><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-2 menu__list-item"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist menu__link--sublist-caret menu__link--active" aria-expanded="true" tabindex="0" href="/help/dev/spec/architecture">Technical specifications</a></div><ul style="display:block;overflow:visible;height:auto" class="menu__list"><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/architecture">Joplin architecture</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/clipper_auth">Clipper authorisation mechanism</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/default_plugins">How to add a new default plugin</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/desktop_styling">Desktop application styling</a></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-3 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist" aria-expanded="false" tabindex="0" href="/help/dev/spec/e2ee/">Encryption</a><button aria-label="Toggle the collapsible sidebar category 'Encryption'" type="button" class="clean-btn menu__caret"></button></div></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/history">Note history</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/interop_with_frontmatter">Markdown with Front Matter Exporter/Importer</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/note_viewer_isolation">Note viewer isolation</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/plugins">Plugin system architecture</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/read_only">Read-only</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/search_sorting">Search Engine</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/server_debug">Debugging Server project with vscode</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/server_delta_sync">Joplin Server delta sync</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/server_file_url_format">Structure of file URLs in Joplin Server</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/server_items">Joplin Server items</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/server_sharing">Joplin Server sharing feature</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/server_sharing_e2ee">Sharing a notebook with E2EE enabled</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/server_user_status">Joplin Server user status</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/sync">Joplin synchronisation</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/sync_lock">Synchronisation locks</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link menu__link--active" aria-current="page" tabindex="0" href="/help/dev/spec/sync_scroll">Synchronous Scroll</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/voice_typing">Voice typing</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-3 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spec/web_app">Web app</a></li></ul></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-2 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist menu__link--sublist-caret" aria-expanded="false" tabindex="0" href="/help/dev/gsoc/gsoc2020/">Google Summer of Code</a></div></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/BUILD">Building the applications</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/DEPLOY">Deploying Joplin apps and scripts</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/build_troubleshooting">Build troubleshooting</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/coding_style">Coding style</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/localisation">Localisation</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/spellcheck">Spellchecking using CSpell</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/help/dev/technical_spec">Guide to writing a technical spec</a></li></ul></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist menu__link--sublist-caret" aria-expanded="false" href="/help/about/changelog/android">About</a></div></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-1 menu__list-item"><a class="menu__link" href="/help/faq">FAQ</a></li></ul></nav></div></div></aside><main class="docMainContainer_gTbr"><div class="container padding-top--md padding-bottom--lg"><div class="row"><div class="col docItemCol_VOVn"><div class="docItemContainer_Djhp"><article><div class="tocCollapsible_ETCw theme-doc-toc-mobile tocMobile_ITEo"><button type="button" class="clean-btn tocCollapsibleButton_TO0P">On this page</button></div><div class="theme-doc-markdown markdown"><h1>Synchronous Scroll</h1><div class="donate-links"><p><a href="https://www.paypal.com/donate/?business=E8JMYD2LQ8MMA&no_recurring=0&item_name=I+rely+on+donations+to+maintain+and+improve+the+Joplin+open+source+project.+Thank+you+for+your+help+-+it+makes+a+difference%21&currency_code=EUR" target="_blank" rel="noopener noreferrer"><img loading="lazy" src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/Donate-PayPal-green.svg" alt="Donate using PayPal" class="img_ev3q"></a> <a href="https://github.com/sponsors/laurent22/" target="_blank" rel="noopener noreferrer"><img loading="lazy" src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/GitHub-Badge.svg" alt="Sponsor on GitHub" class="img_ev3q"></a> <a href="https://www.patreon.com/joplin" target="_blank" rel="noopener noreferrer"><img loading="lazy" src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/Patreon-Badge.svg" alt="Become a patron" class="img_ev3q"></a> <a href="https://joplinapp.org/donate/#donations" target="_blank" rel="noopener noreferrer"><img loading="lazy" src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/Donate-IBAN.svg" alt="Donate using IBAN" class="img_ev3q"></a></p></div><p>Since Joplin 2.6, the synchronous scroll feature (Sync Scroll) for two-pane Markdown Editor is introduced. This document describes its detail.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="motivation">Motivation<a href="#motivation" class="hash-link" aria-label="Direct link to Motivation" title="Direct link to Motivation"></a></h2><p>Joplin has two types of editors, two-pane Markdown Editor and WYSIWYG Editor. Two-pane Markdown Editor consists of an editor pane displaying a Markdown text (Editor) and a viewer pane displaying the rendered Markdown in HTML (Viewer). Both panes have independent scroll bars, and they were linked and controlled by single scroll position expressed as a relative percentage.</p><p><img loading="lazy" src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/sync_scroll/abst-wo-sync2.png" alt="Abstraction View without Sync Scroll" class="img_ev3q"></p><p>Since the height of a Markdown text line is not always proportional to the height of the corresponding HTML element, displayed contents in Editor and Viewer are often inconsistent.</p><p>To keep the consistency of their displayed contents, both scroll bars needed to be controlled separately and synchronously.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="feature-specifications">Feature Specifications<a href="#feature-specifications" class="hash-link" aria-label="Direct link to Feature Specifications" title="Direct link to Feature Specifications"></a></h2><ul><li>Markdown Editor and Viewer scroll synchronously.<ul><li>If you scroll Editor, Viewer is scrolled synchronously. The DOM elements corresponding to Markdown lines you are looking at in Editor's viewport are located in Viewer's viewport as possible.</li><li>The same applies in reverse. (Viewer -<!-- -->><!-- --> Editor)</li><li>During and after any operations, the consistency of scroll positions between Editor and Viewer is kept as possible.</li></ul></li><li>Performance requirement: Since scrolling is a basic GUI operation, its overhead should be minimized.</li></ul><h2 class="anchor anchorWithStickyNavbar_LWe7" id="design">Design<a href="#design" class="hash-link" aria-label="Direct link to Design" title="Direct link to Design"></a></h2><h3 class="anchor anchorWithStickyNavbar_LWe7" id="abstraction-view">Abstraction View<a href="#abstraction-view" class="hash-link" aria-label="Direct link to Abstraction View" title="Direct link to Abstraction View"></a></h3><p>The following figure shows the abstraction view of Sync Scroll. To manage scroll positions, the percent of the line number of a Markdown text data ("percent" in the figure) is used. To handle GUI components and events, "percent" is translated from/to "ePercent" for Editor using translation function E2L() / L2E(). The same is true for Viewer.</p><p><img loading="lazy" src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/sync_scroll/abst-sync-lp2.png" alt="Abstraction View with Sync Scroll" class="img_ev3q"></p><p>The reasons why "ePercent" is not directly translated from "vPercent" and vice versa are:</p><ul><li>Line-based "percent" is good for maintaining, because it is GUI-independent. Both "ePercent" and "vPercent" depend on GUI components and are affected by many factors such as image loading, window resizing, pane hiding/showing.</li><li>Direct conversion between Editor and Viewer needs both Editor and Viewer are present. If one of them is hidden, the information needed for conversion cannot be acquired.</li><li>Editor and Viewer run in different processes, so separation between them makes things simpler and reduces timing issues.</li></ul><h3 class="anchor anchorWithStickyNavbar_LWe7" id="components-and-data-interactions">Components and Data Interactions<a href="#components-and-data-interactions" class="hash-link" aria-label="Direct link to Components and Data Interactions" title="Direct link to Components and Data Interactions"></a></h3><p>The next figure shows the components and data interactions related to Markdown Editor and Sync Scroll.</p><p>First, underlying Markdown Editor is explained. Editor is implemented using <a href="https://codemirror.net/" target="_blank" rel="noopener noreferrer">CodeMirror</a>, and it provides the translation capability between line numbers of Markdown text and their pixel positions in Editor. A Markdown text being edited in Editor is converted to an HTML text using <a href="https://github.com/markdown-it/markdown-it" target="_blank" rel="noopener noreferrer">MarkdownIt</a>, and it is rendered by Viewer, which is implemented using <a href="https://www.electronjs.org/docs/latest/tutorial/web-embeds#iframes" target="_blank" rel="noopener noreferrer">Electron's <code>iframe</code></a>. Editor and Viewer run in different processes, so an HTML text is sent through inter-process communication (ipc). Viewer has the rendered HTML as a DOM and provides the translation capability between DOM elements and their pixel positions in Viewer.</p><p>New additions for Sync Scroll are shown in the figure as red portions. Since a DOM don't know the mapping between lines and elements, mapping information is embedded into an HTML tag as an additional attribute <code>source-line</code> using MarkdownIt. By using the attributes, the translation capability between line numbers and their pixel positions in Viewer can be provided. The information required for the translation is cached in <code>scrollmap</code> described later.</p><p><img loading="lazy" src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/sync_scroll/md-editor-components-pr.png" alt="Components and Data Interactions" class="img_ev3q"></p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="scroll-position-translation">Scroll Position Translation<a href="#scroll-position-translation" class="hash-link" aria-label="Direct link to Scroll Position Translation" title="Direct link to Scroll Position Translation"></a></h3><p>The next figure explains the procedure of the translation from "ePercent" to "percent" and the translation from "percent" to "vPercent". For the translation function E2L(), information acquired from CodeMirror used in Editor is used. For the translation function L2V(), information acquired from HTML DOM which is cached in <code>scrollmap</code> is used.</p><p>To estimate precise positions between lines/elements, linear interpolation algorithm is used as shown in the figure.</p><p><img loading="lazy" src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/sync_scroll/trans-e2l-l2v-2.png" alt="Translation from Editor to Viewer" class="img_ev3q"></p><p>Similarly, the next figure explains the procedure of the translation from "vPercent" to "percent" and the translation from "percent" to "ePercent".</p><p><img loading="lazy" src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/sync_scroll/trans-v2l-l2e-2.png" alt="Translation from Viewer to Editor" class="img_ev3q"></p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="caching-translation-map">Caching Translation Map<a href="#caching-translation-map" class="hash-link" aria-label="Direct link to Caching Translation Map" title="Direct link to Caching Translation Map"></a></h3><p>The translation from "vPercent" to "percent" is a costly task, because it involves scanning the whole DOM. To keep scrolling as light as possible, the information required for the translation is packed into a translation map named <code>scrollmap</code> and is cached for further reuses until outdated.</p><p>Since <code>scrollmap</code> is already sorted, the binary search algorithm can be employed for querying lines or positions. It's faster than the linear search, and a query needed for one scrolling event/action is very light.</p><p>For correct operation, the map should be discarded, when it becomes outdated, such as when a note is switched, when a note is edited, and when the window is resized. Simultaneously, the frequency of recreations should be minimal for users' comfort.</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="implementation-details">Implementation Details<a href="#implementation-details" class="hash-link" aria-label="Direct link to Implementation Details" title="Direct link to Implementation Details"></a></h3><p>The implementation details are out of the scope this document and described in the following pull requests.</p><ul><li><a href="https://github.com/laurent22/joplin/pull/5826" target="_blank" rel="noopener noreferrer">Desktop: Fix #5708: Scroll positions are remembered/preserved (Line-Percent-based Sync Scroll) by ken1kob - Pull Request #5826 - laurent22/joplin</a></li><li><a href="https://github.com/laurent22/joplin/pull/5512" target="_blank" rel="noopener noreferrer">Desktop: Fix #2242: Sync-Scroll for Markdown Editor and Viewer by ken1kob - Pull Request #5512 - laurent22/joplin</a></li></ul></div><footer class="theme-doc-footer docusaurus-mt-lg"><div class="theme-doc-footer-edit-meta-row row"><div class="col"><a href="https://github.com/laurent22/joplin/tree/dev/readme/dev/spec/sync_scroll.md" target="_blank" rel="noreferrer noopener" class="theme-edit-this-page"><svg fill="currentColor" height="20" width="20" viewBox="0 0 40 40" class="iconEdit_Z9Sw" aria-hidden="true"><g><path d="m34.5 11.7l-3 3.1-6.3-6.3 3.1-3q0.5-0.5 1.2-0.5t1.1 0.5l3.9 3.9q0.5 0.4 0.5 1.1t-0.5 1.2z m-29.5 17.1l18.4-18.5 6.3 6.3-18.4 18.4h-6.3v-6.2z"></path></g></svg>Edit this page</a></div><div class="col lastUpdated_vwxv"></div></div></footer></article><nav class="pagination-nav docusaurus-mt-lg" aria-label="Docs pages"><a class="pagination-nav__link pagination-nav__link--prev" href="/help/dev/spec/sync_lock"><div class="pagination-nav__sublabel">Previous</div><div class="pagination-nav__label">Synchronisation locks</div></a><a class="pagination-nav__link pagination-nav__link--next" href="/help/dev/spec/voice_typing"><div class="pagination-nav__sublabel">Next</div><div class="pagination-nav__label">Voice typing</div></a></nav></div></div><div class="col col--3"><div class="tableOfContents_bqdL thin-scrollbar theme-doc-toc-desktop"><ul class="table-of-contents table-of-contents__left-border"><li><a href="#motivation" class="table-of-contents__link toc-highlight">Motivation</a></li><li><a href="#feature-specifications" class="table-of-contents__link toc-highlight">Feature Specifications</a></li><li><a href="#design" class="table-of-contents__link toc-highlight">Design</a><ul><li><a href="#abstraction-view" class="table-of-contents__link toc-highlight">Abstraction View</a></li><li><a href="#components-and-data-interactions" class="table-of-contents__link toc-highlight">Components and Data Interactions</a></li><li><a href="#scroll-position-translation" class="table-of-contents__link toc-highlight">Scroll Position Translation</a></li><li><a href="#caching-translation-map" class="table-of-contents__link toc-highlight">Caching Translation Map</a></li><li><a href="#implementation-details" class="table-of-contents__link toc-highlight">Implementation Details</a></li></ul></li></ul></div></div></div></div></main></div></div><footer class="footer footer--dark"><div class="container container-fluid"><div class="row footer__links"><div class="col footer__col"><div class="footer__title">Community</div><ul class="footer__items clean-list"><li class="footer__item"><a href="https://twitter.com/joplinapp" target="_blank" rel="noopener noreferrer" class="footer__link-item">Twitter<svg width="13.5" height="13.5" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_nPIU"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></a></li><li class="footer__item"><a href="https://www.patreon.com/joplin" target="_blank" rel="noopener noreferrer" class="footer__link-item">Patreon<svg width="13.5" height="13.5" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_nPIU"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></a></li><li class="footer__item"><a href="https://www.linkedin.com/company/joplin" target="_blank" rel="noopener noreferrer" class="footer__link-item">LinkedIn<svg width="13.5" height="13.5" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_nPIU"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></a></li><li class="footer__item"><a href="https://discord.gg/VSj7AFHvpq" target="_blank" rel="noopener noreferrer" class="footer__link-item">Discord<svg width="13.5" height="13.5" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_nPIU"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></a></li><li class="footer__item"><a href="https://mastodon.social/@joplinapp" target="_blank" rel="noopener noreferrer" class="footer__link-item">Mastodon<svg width="13.5" height="13.5" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_nPIU"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></a></li><li class="footer__item"><a href="https://sopuli.xyz/c/joplinapp" target="_blank" rel="noopener noreferrer" class="footer__link-item">Lemmy<svg width="13.5" height="13.5" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_nPIU"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></a></li><li class="footer__item"><a href="https://github.com/laurent22/joplin/" target="_blank" rel="noopener noreferrer" class="footer__link-item">GitHub<svg width="13.5" height="13.5" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_nPIU"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></a></li></ul></div><div class="col footer__col"><div class="footer__title">Legal</div><ul class="footer__items clean-list"><li class="footer__item"><a href="https://joplinapp.org/privacy" target="_blank" rel="noopener noreferrer" class="footer__link-item">Privacy Policy</a></li></ul></div></div><div class="footer__bottom text--center"><div class="footer__copyright">Copyright © 2016-2024 Laurent Cozic</div></div></div></footer></div>
|
||
<script src="/assets/js/runtime~main.1fb4f966.js"></script>
|
||
<script src="/assets/js/main.aa9faae4.js"></script>
|
||
</body>
|
||
</html> |