Skip to content

Commit 8529392

Browse files
committed
feat(mdviewer): sync toolbar state from CM cursor position
When clicking in CodeMirror, parse the current line's markdown syntax to determine block type (H1-H6, paragraph), list/table/code block context, and inline formatting (bold, italic, strikethrough). Send MDVIEWR_TOOLBAR_STATE message to the iframe so the embedded toolbar reflects the CM cursor position. Toolbar state sync runs independently of cursor sync toggle.
1 parent 55ab3e3 commit 8529392

2 files changed

Lines changed: 88 additions & 2 deletions

File tree

src-mdviewer/src/bridge.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,11 @@ export function initBridge() {
189189
case "MDVIEWR_RERENDER_CONTENT":
190190
handleRerenderContent(data);
191191
break;
192+
case "MDVIEWR_TOOLBAR_STATE":
193+
if (data.state) {
194+
emit("editor:selection-state", data.state);
195+
}
196+
break;
192197
case "MDVIEWR_IMAGE_UPLOAD_RESULT":
193198
_handleImageUploadResult(data);
194199
break;

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

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,14 @@ define(function (require, exports, module) {
183183
};
184184
ThemeManager.on("themeChange", _themeChangeHandler);
185185

186-
// Listen for cursor activity in CM5 for scroll sync and selection sync (CM5 → iframe)
186+
// Listen for cursor activity in CM5 for scroll sync, selection sync, and toolbar state (CM5 → iframe)
187187
_cursorHandler = function () {
188-
if (_syncingFromIframe || !_iframeReady || !_cursorSyncEnabled) {
188+
if (_syncingFromIframe || !_iframeReady) {
189+
return;
190+
}
191+
// Toolbar state sync always runs (independent of cursor sync toggle)
192+
_syncToolbarStateToIframe();
193+
if (!_cursorSyncEnabled) {
189194
return;
190195
}
191196
clearTimeout(_scrollSyncTimer);
@@ -509,6 +514,82 @@ define(function (require, exports, module) {
509514
}, "*");
510515
}
511516

517+
/**
518+
* Parse the current CM line to determine the block type and formatting context,
519+
* then send it to the iframe so the toolbar can reflect CM cursor position.
520+
*/
521+
function _syncToolbarStateToIframe() {
522+
if (!_active || !_iframeReady) {
523+
return;
524+
}
525+
const iframeWindow = _getIframeWindow();
526+
if (!iframeWindow) {
527+
return;
528+
}
529+
const cm = _getCM();
530+
if (!cm) {
531+
return;
532+
}
533+
const cursor = cm.getCursor();
534+
const lineText = cm.getLine(cursor.line) || "";
535+
const trimmed = lineText.trimStart();
536+
537+
// Determine block type from markdown syntax
538+
let blockType = "P";
539+
if (/^#{1}\s/.test(trimmed)) { blockType = "H1"; }
540+
else if (/^#{2}\s/.test(trimmed)) { blockType = "H2"; }
541+
else if (/^#{3}\s/.test(trimmed)) { blockType = "H3"; }
542+
else if (/^#{4}\s/.test(trimmed)) { blockType = "H4"; }
543+
else if (/^#{5}\s/.test(trimmed)) { blockType = "H5"; }
544+
else if (/^#{6}\s/.test(trimmed)) { blockType = "H6"; }
545+
546+
// Check context by scanning surrounding lines
547+
let inList = /^\s*[-*+]\s/.test(lineText) || /^\s*\d+\.\s/.test(lineText);
548+
let inTable = lineText.trim().startsWith("|") && lineText.trim().endsWith("|");
549+
let inCodeBlock = false;
550+
551+
// Check if we're inside a fenced code block by counting ``` above
552+
let fenceCount = 0;
553+
for (let i = 0; i < cursor.line; i++) {
554+
if (/^```/.test(cm.getLine(i).trimStart())) {
555+
fenceCount++;
556+
}
557+
}
558+
inCodeBlock = fenceCount % 2 === 1;
559+
560+
// Detect formatting around cursor
561+
let bold = false;
562+
let italic = false;
563+
let underline = false;
564+
let strikethrough = false;
565+
566+
// Simple inline format detection: check if cursor is within ** ** or * * etc.
567+
const beforeCursor = lineText.substring(0, cursor.ch);
568+
const afterCursor = lineText.substring(cursor.ch);
569+
const fullContext = beforeCursor + afterCursor;
570+
// Count unescaped markers before cursor
571+
const boldBefore = (beforeCursor.match(/\*\*/g) || []).length;
572+
const italicBefore = (beforeCursor.replace(/\*\*/g, "").match(/\*/g) || []).length;
573+
bold = boldBefore % 2 === 1;
574+
italic = italicBefore % 2 === 1;
575+
strikethrough = (beforeCursor.match(/~~/g) || []).length % 2 === 1;
576+
577+
iframeWindow.postMessage({
578+
type: "MDVIEWR_TOOLBAR_STATE",
579+
state: {
580+
blockType: blockType,
581+
bold: bold,
582+
italic: italic,
583+
underline: underline,
584+
strikethrough: strikethrough,
585+
inTable: inTable,
586+
inList: inList,
587+
inCodeBlock: inCodeBlock,
588+
inHeading: blockType !== "P"
589+
}
590+
}, "*");
591+
}
592+
512593
/**
513594
* Move the CM5 cursor to the given source line (1-based) and scroll
514595
* the editor to show it if it's not already visible.

0 commit comments

Comments
 (0)