Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
32 changes: 31 additions & 1 deletion blocks/canvas/editor-utils/blocks.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DOMParser as PMDOMParser, NodeSelection } from 'da-y-wrapper';
import { DOMParser as PMDOMParser, NodeSelection, TextSelection } from 'da-y-wrapper';

const NON_BLOCK_TABLE_NAMES = new Set(['metadata', 'section metadata', 'section-metadata']);

Expand Down Expand Up @@ -223,6 +223,36 @@ export function appendBlockRow(view, tablePos, rowDom) {
view.dispatch(tr.scrollIntoView());
}

/** Doc range of each section, split on horizontal rules (see deleteSection). */
export function getSectionRanges(view) {
const { doc, schema } = view.state;
const ranges = [];
let current = null;
doc.forEach((node, offset) => {
if (node.type === schema.nodes.horizontal_rule) {
current = null;
return;
}
if (!current) {
current = { from: offset, to: offset };
ranges.push(current);
}
current.to = offset + node.nodeSize;
});
return ranges;
}

/** Select a whole section and scroll it into view. */
export function selectSection(view, sectionIndex) {
if (!view) return false;
const range = getSectionRanges(view)[sectionIndex];
if (!range) return false;
const { tr, doc } = view.state;
view.dispatch(tr.setSelection(TextSelection.create(doc, range.from, range.to)).scrollIntoView());
view.focus();
return true;
}

export function deleteSection(view, sectionIndex) {
if (!view) return;
const { doc, schema } = view.state;
Expand Down
21 changes: 21 additions & 0 deletions blocks/canvas/ew-panel-extensions/iframe-protocol.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { insertText, insertHTML, getEditorSelection } from './helpers.js';
import { selectSection } from '../editor-utils/blocks.js';
import { canvasBus } from '../utils/canvas-bus.js';
import { getNx } from '../../../scripts/utils.js';
import { getPostMessageTargetOrigin, isValidHref } from '../../shared/utils.js';

Expand Down Expand Up @@ -61,6 +63,25 @@ export async function setupIframeChannel({ iframe, hashState, getView, onClose }
);
}

// Navigation reuses the outline's own path — same blockIndex address, same
// scroll — rather than driving the view directly, so the outline highlight
// and doc state stay in step.
if (action === 'scrollToBlock') {
const ok = Number.isInteger(details?.blockIndex) && details.blockIndex >= 0;
if (ok) canvasBus.editorSelectState.emit({ blockIndex: details.blockIndex, source: 'plugin' });
channel.port1.postMessage({ action: 'scrollToBlockResult', details: { ok } });
return;
}

// Separate from navigation: a section range is the only way to reach section
// metadata, which getBlockPositions deliberately excludes from blockIndex.
// Leaves a replaceable range under sendHTML.
if (action === 'selectSection') {
const ok = editorView ? selectSection(editorView, details?.index) : false;
channel.port1.postMessage({ action: 'selectSectionResult', details: { ok } });
return;
}

if (action === 'getSelection') {
if (!editorView) {
channel.port1.postMessage({ action: 'error', details: 'No editor view' });
Expand Down
Loading