Skip to content

Commit 276e6de

Browse files
committed
feat(mdviewer): simplify mode controls (Option D+)
Hide Phoenix play button and mode dropdown for MD files — md iframe handles its own Edit/Reader toggle. Add cursor sync toggle (link-2 / link-2-off icons) in md toolbar. Remove _syncMdviewrPreviewMode complexity. Preserve edit mode across reload.
1 parent e0e6e11 commit 276e6de

File tree

6 files changed

+90
-16
lines changed

6 files changed

+90
-16
lines changed

src-mdviewer/src/bridge.js

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ let _syncId = 0;
1313
let _lastReceivedSyncId = -1;
1414
let _suppressContentChange = false;
1515
let _baseURL = "";
16-
let _pendingReloadScroll = null; // { filePath, scrollPos, editMode } for scroll restore after reload
16+
let _pendingReloadScroll = null; // { filePath, scrollSourceLine } for scroll restore after reload
1717

1818
/**
1919
* Check if a URL is absolute (not relative to the document).
@@ -175,6 +175,7 @@ export function initBridge() {
175175

176176
// Detect source line from data-source-line attributes for scroll sync.
177177
// In read mode, also refocus CM5 unless the user has a text selection.
178+
// Disabled in preview mode (no cursor sync).
178179
document.addEventListener("click", (e) => {
179180
const sourceLine = _getSourceLineFromElement(e.target);
180181
if (getState().editMode) {
@@ -222,6 +223,11 @@ export function initBridge() {
222223
sendToParent("mdviewrRequestEditMode", {});
223224
});
224225

226+
// Cursor sync toggle
227+
on("toggle:cursorSync", ({ enabled }) => {
228+
sendToParent("mdviewrCursorSyncToggle", { enabled });
229+
});
230+
225231
// Notify parent that iframe is ready
226232
sendToParent("mdviewrReady", {});
227233
}
@@ -359,12 +365,13 @@ function handleSwitchFile(data) {
359365
docCache.createEntry(filePath, markdown, parseResult);
360366
docCache.switchTo(filePath);
361367

362-
// Restore scroll position from reload if applicable
368+
// Restore scroll position and edit mode from reload if applicable
363369
if (_pendingReloadScroll && _pendingReloadScroll.filePath === filePath) {
364370
const entry = docCache.getEntry(filePath);
365371
if (entry) {
366372
entry._scrollSourceLine = _pendingReloadScroll.scrollSourceLine;
367373
}
374+
const restoreEditMode = _pendingReloadScroll.editMode;
368375
_pendingReloadScroll = null;
369376

370377
setState({
@@ -385,6 +392,10 @@ function handleSwitchFile(data) {
385392
}
386393
});
387394
}
395+
396+
if (restoreEditMode) {
397+
setState({ editMode: true });
398+
}
388399
} else {
389400
setState({
390401
currentContent: markdown,
@@ -442,11 +453,12 @@ function handleReloadFile(data) {
442453
const activeEntry = docCache.getEntry(filePath);
443454
if (activeEntry) {
444455
const scrollSourceLine = activeEntry._scrollSourceLine;
445-
if (getState().editMode) {
456+
const wasEditMode = getState().editMode;
457+
if (wasEditMode) {
446458
setState({ editMode: false });
447459
}
448460
docCache.removeEntry(filePath);
449-
_pendingReloadScroll = { filePath, scrollSourceLine };
461+
_pendingReloadScroll = { filePath, scrollSourceLine, editMode: wasEditMode };
450462
}
451463
} else {
452464
docCache.removeEntry(filePath);
@@ -471,6 +483,7 @@ function handleSetEditMode(data) {
471483
setState({ editMode });
472484
}
473485

486+
474487
function handleSetLocale(data) {
475488
const { locale } = data;
476489
if (locale) {

src-mdviewer/src/components/embedded-toolbar.js

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ import {
2727
ChevronDown,
2828
Type,
2929
MoreHorizontal,
30-
BookOpen
30+
BookOpen,
31+
Link2,
32+
Link2Off
3133
} from "lucide";
3234
import { on, emit } from "../core/events.js";
3335
import { getState, setState } from "../core/state.js";
@@ -43,7 +45,7 @@ const THRESHOLD_LISTS = 520; // then lists
4345
const THRESHOLD_TEXT = 420; // finally text formatting
4446

4547
const allIcons = { Bold, Italic, Strikethrough, Underline, Code, Link, List, ListOrdered,
46-
ListChecks, Quote, Minus, Table, FileCode, ChevronDown, Type, MoreHorizontal, Pencil, BookOpen };
48+
ListChecks, Quote, Minus, Table, FileCode, ChevronDown, Type, MoreHorizontal, Pencil, BookOpen, Link2, Link2Off };
4749

4850
export function initEmbeddedToolbar() {
4951
toolbar = document.getElementById("toolbar");
@@ -76,13 +78,19 @@ function render() {
7678
function renderReadMode() {
7779
toolbar.innerHTML = `<div class="embedded-toolbar">
7880
<div class="toolbar-spacer"></div>
81+
<button class="toolbar-btn cursor-sync-btn active" id="emb-cursor-sync" data-tooltip="${t("toolbar.cursor_sync") || "Cursor sync"}" aria-pressed="true">
82+
<i data-lucide="link-2" class="sync-on-icon"></i>
83+
<i data-lucide="link-2-off" class="sync-off-icon" style="display:none"></i>
84+
</button>
7985
<button class="edit-toggle-btn" id="emb-edit-btn" title="${t("toolbar.switch_to_edit") || "Switch to edit mode"}">
8086
<i data-lucide="pencil"></i>
8187
<span>${t("toolbar.edit") || "Edit"}</span>
8288
</button>
8389
</div>`;
8490

85-
createIcons({ icons: { Pencil }, attrs: { class: "" } });
91+
createIcons({ icons: { Pencil, Link2, Link2Off }, attrs: { class: "" } });
92+
93+
wireCursorSyncButton();
8694

8795
const editBtn = document.getElementById("emb-edit-btn");
8896
if (editBtn) {
@@ -166,6 +174,10 @@ function renderEditMode(level) {
166174
toolbar.innerHTML = `<div class="embedded-toolbar">
167175
${formatRow}
168176
<div class="toolbar-spacer"></div>
177+
<button class="toolbar-btn cursor-sync-btn active" id="emb-cursor-sync" data-tooltip="${t("toolbar.cursor_sync") || "Cursor sync"}" aria-pressed="true">
178+
<i data-lucide="link-2" class="sync-on-icon"></i>
179+
<i data-lucide="link-2-off" class="sync-off-icon" style="display:none"></i>
180+
</button>
169181
<button class="done-btn" id="emb-done-btn" title="${t("toolbar.switch_to_reader") || "Switch to reader mode"}">
170182
<i data-lucide="book-open"></i>
171183
<span>${t("toolbar.reader") || "Reader"}</span>
@@ -179,6 +191,7 @@ function renderEditMode(level) {
179191
if (level > 0) {
180192
wireDropdowns();
181193
}
194+
wireCursorSyncButton();
182195
wireDoneButton();
183196
}
184197

@@ -251,6 +264,21 @@ function closeAllDropdowns() {
251264
}
252265
}
253266

267+
function wireCursorSyncButton() {
268+
const syncBtn = document.getElementById("emb-cursor-sync");
269+
if (syncBtn) {
270+
syncBtn.addEventListener("click", () => {
271+
const isActive = syncBtn.classList.toggle("active");
272+
syncBtn.setAttribute("aria-pressed", String(isActive));
273+
const onIcon = syncBtn.querySelector(".sync-on-icon");
274+
const offIcon = syncBtn.querySelector(".sync-off-icon");
275+
if (onIcon) onIcon.style.display = isActive ? "" : "none";
276+
if (offIcon) offIcon.style.display = isActive ? "none" : "";
277+
emit("toggle:cursorSync", { enabled: isActive });
278+
});
279+
}
280+
}
281+
254282
function wireDoneButton() {
255283
const doneBtn = document.getElementById("emb-done-btn");
256284
if (doneBtn) {

src-mdviewer/src/locales/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
"more": "More",
1818
"reader": "Reader",
1919
"switch_to_reader": "Switch to reader mode",
20-
"switch_to_edit": "Switch to edit mode"
20+
"switch_to_edit": "Switch to edit mode",
21+
"cursor_sync": "Toggle cursor sync"
2122
},
2223
"sidebar": {
2324
"toc": "Table of Contents",

src-mdviewer/src/styles/app.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@ html, body {
100100
background: var(--color-accent-subtle);
101101
}
102102

103+
.cursor-sync-btn,
104+
.cursor-sync-btn.active {
105+
color: var(--color-text-secondary);
106+
background: transparent;
107+
}
108+
103109
.toolbar-btn svg {
104110
width: 14px;
105111
height: 14px;

src/extensionsIntegrated/Phoenix-live-preview/MarkdownSync.js

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ define(function (require, exports, module) {
4545
let _cursorHandler = null;
4646
let _onEditModeRequest = null;
4747
let _onIframeReadyCallback = null;
48+
let _cursorSyncEnabled = true;
4849

4950
const DEBOUNCE_TO_IFRAME_MS = 150;
5051
const SCROLL_SYNC_DEBOUNCE_MS = 100;
@@ -110,19 +111,24 @@ define(function (require, exports, module) {
110111
_onEditModeRequest();
111112
}
112113
break;
114+
case "mdviewrCursorSyncToggle":
115+
_cursorSyncEnabled = !!data.enabled;
116+
break;
113117
case "embeddedIframeFocusEditor":
114118
if (data.sourceLine != null) {
115119
_scrollCMToLine(data.sourceLine);
116120
}
117121
utils.focusActiveEditorIfFocusInLivePreview();
118122
break;
119123
case "mdviewrScrollSync":
120-
if (data.sourceLine != null) {
124+
if (_cursorSyncEnabled && data.sourceLine != null) {
121125
_scrollCMToLine(data.sourceLine);
122126
}
123127
break;
124128
case "mdviewrSelectionSync":
125-
_handleSelectionFromIframe(data);
129+
if (_cursorSyncEnabled) {
130+
_handleSelectionFromIframe(data);
131+
}
126132
break;
127133
case "embeddedIframeHrefClick":
128134
_handleHrefClick(data);
@@ -157,7 +163,7 @@ define(function (require, exports, module) {
157163

158164
// Listen for cursor activity in CM5 for scroll sync and selection sync (CM5 → iframe)
159165
_cursorHandler = function () {
160-
if (_syncingFromIframe || !_iframeReady) {
166+
if (_syncingFromIframe || !_iframeReady || !_cursorSyncEnabled) {
161167
return;
162168
}
163169
clearTimeout(_scrollSyncTimer);
@@ -651,11 +657,20 @@ define(function (require, exports, module) {
651657
_onIframeReadyCallback = handler;
652658
}
653659

660+
/**
661+
* Enable or disable cursor/scroll sync between CM and mdviewer.
662+
* Content sync still works regardless.
663+
*/
664+
function setCursorSyncEnabled(enabled) {
665+
_cursorSyncEnabled = enabled;
666+
}
667+
654668
exports.activate = activate;
655669
exports.deactivate = deactivate;
656670
exports.isActive = isActive;
657671
exports.reloadCurrentFile = reloadCurrentFile;
658672
exports.setEditModeRequestHandler = setEditModeRequestHandler;
659673
exports.setEditMode = setEditMode;
660674
exports.setIframeReadyHandler = setIframeReadyHandler;
675+
exports.setCursorSyncEnabled = setCursorSyncEnabled;
661676
});

src/extensionsIntegrated/Phoenix-live-preview/main.js

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -324,9 +324,19 @@ define(function (require, exports, module) {
324324
}
325325

326326
/**
327-
* update the mode button text in the live preview toolbar UI based on the current mode
328-
* @param {String} mode - The current mode ("preview", "highlight", or "edit")
327+
* Hide the play button and mode dropdown when mdviewer is active,
328+
* since MD files have their own Edit/Reader toggle in the iframe toolbar.
329+
* Does not hide in custom server mode (handled by _isMdviewrActive being false).
329330
*/
331+
function _updateLPControlsForMdviewer() {
332+
if ($previewBtn) {
333+
$previewBtn.toggle(!_isMdviewrActive);
334+
}
335+
if ($modeBtn) {
336+
$modeBtn.toggle(!_isMdviewrActive);
337+
}
338+
}
339+
330340
function _updateModeButton(mode) {
331341
if ($modeBtn) {
332342
if (mode === "highlight") {
@@ -910,8 +920,8 @@ define(function (require, exports, module) {
910920

911921
_isMdviewrActive = true;
912922
MarkdownSync.activate(currentDoc, $iframe, baseURL);
913-
// Set initial edit mode based on entitlement (for reuse case where iframe is already ready)
914-
MarkdownSync.setEditMode(isProEditUser);
923+
// Sync preview mode and edit mode for reuse case where iframe is already ready
924+
_updateLPControlsForMdviewer();
915925

916926
Metrics.countEvent(Metrics.EVENT_TYPE.LIVE_PREVIEW, "render", "mdviewr");
917927
}
@@ -945,6 +955,7 @@ define(function (require, exports, module) {
945955
if ($mdviewrIframe) {
946956
$mdviewrIframe.hide();
947957
}
958+
_updateLPControlsForMdviewer();
948959
}
949960
let newSrc = encodeURI(previewDetails.URL);
950961
if($iframe.attr('src') === newSrc && !force){
@@ -1543,7 +1554,7 @@ define(function (require, exports, module) {
15431554

15441555
// When iframe first loads, send initial edit mode based on entitlement
15451556
MarkdownSync.setIframeReadyHandler(function () {
1546-
MarkdownSync.setEditMode(isProEditUser);
1557+
_updateLPControlsForMdviewer();
15471558
});
15481559

15491560
_projectOpened();

0 commit comments

Comments
 (0)