mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-10-01 09:19:34 +05:30
feat(flowchart): bundle-side clipboard wire-up (C11)
Cmd+C / Cmd+V / Cmd+D keyboard shortcuts now work in the standalone
window. The clipboard pure module (committed in C6a) is inlined
into the bundle alongside the mermaid parser (C9 inlining) so the
standalone window does not depend on a separate bundle rebuild.
- copySelection / pasteSelection definitions added inside the
bundle IIFE, exposed as window.FlowchartClipboard for jsdom
tests.
- Module-level _clipboard variable scoped to the keydown handler
block so copy survives across Cmd+V and Cmd+D presses.
- Cmd+C: serialises the canvas-owned selection (now read via
getSelection(), not the old DOM .selected query) and stashes it.
- Cmd+V: applies the stashed payload to the store via addNode +
connect; nothing to do for the edge-only payload.
- Cmd+D: copy + paste in one keystroke, with a 24px offset.
- Delete/Backspace: reuses getSelection() to delete the selected
node or edge; replaces the fragile DOM-querying path that the
test for the bundle was no longer catching.
The pure module in src/flowchart/flowchart-clipboard.js stays the
source of truth for the algorithm (9 tests in C6a). Bundle inline
keeps the standalone window functional without a rebuild step;
a future build-time inclusion would collapse the duplication.
Amit Haridas
This commit is contained in:
@@ -1633,9 +1633,10 @@
|
|||||||
if (els.btnRedo) els.btnRedo.disabled = !_store.canRedo();
|
if (els.btnRedo) els.btnRedo.disabled = !_store.canRedo();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keyboard shortcuts — Ctrl/Cmd+Z / Ctrl/Cmd+Shift+Z. Delete / Backspace
|
// Keyboard shortcuts — Ctrl/Cmd+Z / Ctrl/Cmd+Shift+Z. v4.13.0 — added
|
||||||
// are intentionally NOT wired (v4.11.0 — there is no canvas selection
|
// copy/paste/duplicate (Cmd+C / Cmd+V / Cmd+D) plus Delete/Backspace
|
||||||
// state anymore; use the × buttons in the node-list panel instead).
|
// for the canvas-owned selection (replaces the old DOM .selected read).
|
||||||
|
let _clipboard = null; // module-level clipboard for Cmd+C/V/D
|
||||||
document.addEventListener('keydown', (ev) => {
|
document.addEventListener('keydown', (ev) => {
|
||||||
if (!_store) return;
|
if (!_store) return;
|
||||||
const meta = ev.ctrlKey || ev.metaKey;
|
const meta = ev.ctrlKey || ev.metaKey;
|
||||||
@@ -1647,6 +1648,48 @@
|
|||||||
if (meta && ev.shiftKey && ev.key.toLowerCase() === 'z') {
|
if (meta && ev.shiftKey && ev.key.toLowerCase() === 'z') {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
_store.redo();
|
_store.redo();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (meta && !ev.shiftKey && ev.key.toLowerCase() === 'c') {
|
||||||
|
const sel = _canvas ? _canvas.getSelection() : { nodeId: null, edgeId: null };
|
||||||
|
const payload = copySelection(_store.getGraph(), sel);
|
||||||
|
if (payload) {
|
||||||
|
_clipboard = payload;
|
||||||
|
setStatus('Copied');
|
||||||
|
ev.preventDefault();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (meta && !ev.shiftKey && ev.key.toLowerCase() === 'v') {
|
||||||
|
if (!_clipboard) return;
|
||||||
|
const created = pasteSelection(_clipboard, _store.getGraph(), _store);
|
||||||
|
if (created.length > 0) {
|
||||||
|
setStatus('Pasted');
|
||||||
|
ev.preventDefault();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (meta && !ev.shiftKey && ev.key.toLowerCase() === 'd') {
|
||||||
|
const sel = _canvas ? _canvas.getSelection() : { nodeId: null, edgeId: null };
|
||||||
|
const payload = copySelection(_store.getGraph(), sel);
|
||||||
|
if (payload) {
|
||||||
|
const created = pasteSelection(payload, _store.getGraph(), _store);
|
||||||
|
if (created.length > 0) {
|
||||||
|
setStatus('Duplicated');
|
||||||
|
ev.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ev.key === 'Delete' || ev.key === 'Backspace') {
|
||||||
|
const sel = _canvas ? _canvas.getSelection() : { nodeId: null, edgeId: null };
|
||||||
|
if (sel.nodeId) {
|
||||||
|
ev.preventDefault();
|
||||||
|
_store.removeNode(sel.nodeId);
|
||||||
|
} else if (sel.edgeId) {
|
||||||
|
ev.preventDefault();
|
||||||
|
_store.disconnect(sel.edgeId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1677,6 +1720,64 @@
|
|||||||
// these by closure; this handle exists purely for unit tests.
|
// these by closure; this handle exists purely for unit tests.
|
||||||
window.FlowchartModals = { promptInline, confirmInline };
|
window.FlowchartModals = { promptInline, confirmInline };
|
||||||
|
|
||||||
|
// ========== flowchart-clipboard (inlined v4.13.0) ==========
|
||||||
|
// Same source as src/flowchart/flowchart-clipboard.js (9 tests there).
|
||||||
|
// Duplicated into the bundle so the standalone window doesn't need a
|
||||||
|
// rebuild step. Logic stays in sync with the pure module.
|
||||||
|
function copySelection(graph, selection) {
|
||||||
|
if (!graph || !selection) return null;
|
||||||
|
if (selection.nodeId) {
|
||||||
|
const node = graph.nodes.find((n) => n.id === selection.nodeId);
|
||||||
|
if (!node) return null;
|
||||||
|
const connectedEdges = graph.edges.filter(
|
||||||
|
(e) => e.fromNodeId === node.id || e.toNodeId === node.id
|
||||||
|
);
|
||||||
|
return { version: 1, kind: 'node-with-edges', node, edges: connectedEdges };
|
||||||
|
}
|
||||||
|
if (selection.edgeId) {
|
||||||
|
const edge = graph.edges.find((e) => e.id === selection.edgeId);
|
||||||
|
if (!edge) return null;
|
||||||
|
return { version: 1, kind: 'edge', edge };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
function pasteSelection(payload, graph, store, opts) {
|
||||||
|
if (!payload) return [];
|
||||||
|
const dx = (opts && opts.offsetX) || 24;
|
||||||
|
const dy = (opts && opts.offsetY) || 24;
|
||||||
|
if (payload.kind === 'node-with-edges' && payload.node) {
|
||||||
|
const old = payload.node;
|
||||||
|
const created = store.addNode({
|
||||||
|
kind: old.kind,
|
||||||
|
x: (old.x || 0) + dx,
|
||||||
|
y: (old.y || 0) + dy,
|
||||||
|
label: old.label,
|
||||||
|
color: old.color,
|
||||||
|
});
|
||||||
|
const remap = new Map([[old.id, created.id]]);
|
||||||
|
for (const e of payload.edges || []) {
|
||||||
|
const from = remap.get(e.fromNodeId) || e.fromNodeId;
|
||||||
|
const to = remap.get(e.toNodeId) || e.toNodeId;
|
||||||
|
if (graph.nodes.find((n) => n.id === from) && graph.nodes.find((n) => n.id === to)) {
|
||||||
|
store.connect(from, to, e.kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [{ id: created.id }];
|
||||||
|
}
|
||||||
|
if (payload.kind === 'edge' && payload.edge) {
|
||||||
|
const e = payload.edge;
|
||||||
|
if (
|
||||||
|
graph.nodes.find((n) => n.id === e.fromNodeId) &&
|
||||||
|
graph.nodes.find((n) => n.id === e.toNodeId)
|
||||||
|
) {
|
||||||
|
store.connect(e.fromNodeId, e.toNodeId, e.kind);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
window.FlowchartClipboard = { copySelection, pasteSelection };
|
||||||
|
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
document.addEventListener('DOMContentLoaded', bootstrap);
|
document.addEventListener('DOMContentLoaded', bootstrap);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user