From 408cf7ea884a87f3267097077c3a9f8a563456da Mon Sep 17 00:00:00 2001 From: d3m1d0v Date: Thu, 15 Feb 2024 13:05:54 +0300 Subject: [PATCH] feat(YfmCut): add border to editable yfm-cut nodes --- src/extensions/yfm/YfmCut/index.scss | 4 ++ src/extensions/yfm/YfmCut/index.ts | 2 + src/extensions/yfm/YfmCut/plugins/active.ts | 65 +++++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 src/extensions/yfm/YfmCut/plugins/active.ts diff --git a/src/extensions/yfm/YfmCut/index.scss b/src/extensions/yfm/YfmCut/index.scss index 43a42d1d..91e7d622 100644 --- a/src/extensions/yfm/YfmCut/index.scss +++ b/src/extensions/yfm/YfmCut/index.scss @@ -2,4 +2,8 @@ .ProseMirror.yfm .yfm-cut { @include mixins.block-border-hover(); + + &.yfm-cut-active { + border-color: var(--g-color-line-generic); + } } diff --git a/src/extensions/yfm/YfmCut/index.ts b/src/extensions/yfm/YfmCut/index.ts index a576bced..261d5d75 100644 --- a/src/extensions/yfm/YfmCut/index.ts +++ b/src/extensions/yfm/YfmCut/index.ts @@ -9,6 +9,7 @@ import {createYfmCut, toYfmCut} from './actions/toYfmCut'; import {backToCutTitle, exitFromCutTitle, liftEmptyBlockFromCut, removeCut} from './commands'; import {cutType} from './const'; import {YfmCutTitleNodeView} from './nodeviews/yfm-cut-title'; +import {cutActivePlugin} from './plugins/active'; import {cutAutoOpenPlugin} from './plugins/auto-open'; import './index.scss'; @@ -39,6 +40,7 @@ export const YfmCut: ExtensionAuto = (builder, opts) => { }); builder + .addPlugin(cutActivePlugin) .addPlugin(cutAutoOpenPlugin) .addAction(cutAction, () => toYfmCut) .addKeymap(() => ({ diff --git a/src/extensions/yfm/YfmCut/plugins/active.ts b/src/extensions/yfm/YfmCut/plugins/active.ts new file mode 100644 index 00000000..f8764701 --- /dev/null +++ b/src/extensions/yfm/YfmCut/plugins/active.ts @@ -0,0 +1,65 @@ +import type {NodeType, ResolvedPos} from 'prosemirror-model'; +import {Plugin} from 'prosemirror-state'; +import type {NodeWithPos} from 'prosemirror-utils'; +import {Decoration, DecorationSet} from 'prosemirror-view'; + +import {isNodeSelection, isTextSelection} from '../../../../utils/selection'; +import {cutType} from '../const'; + +const YFM_CUT_ACTIVE_CLASSNAME = 'yfm-cut-active'; + +export const cutActivePlugin = () => { + return new Plugin({ + props: { + decorations(state) { + const decos: Decoration[] = []; + const sel = state.selection; + const yfmCutType = cutType(state.schema); + + const createDeco = ({pos, node}: NodeWithPos) => { + decos.push( + Decoration.node(pos, pos + node.nodeSize, { + class: YFM_CUT_ACTIVE_CLASSNAME, + }), + ); + }; + + if (isNodeSelection(sel)) { + if (sel.node.type === yfmCutType) createDeco({pos: sel.from, node: sel.node}); + findParentNodesOfType(yfmCutType, sel.$from).forEach(createDeco); + } else if (isTextSelection(sel)) { + findParentNodesOfType(yfmCutType, sel.$from).forEach(createDeco); + if (!sel.$from.sameParent(sel.$to)) { + findParentNodesOfType(yfmCutType, sel.$to).forEach(createDeco); + state.doc.nodesBetween(sel.from, sel.to, (node, pos) => { + if (node.type === yfmCutType) createDeco({pos, node}); + if (node.isTextblock) return false; + return true; + }); + } + } else { + // some other selection + findParentNodesOfType(yfmCutType, sel.$from).forEach(createDeco); + } + + return decos.length ? DecorationSet.create(state.doc, decos) : DecorationSet.empty; + }, + }, + }); +}; + +function findParentNodesOfType( + type: NodeType, + $pos: ResolvedPos, +): (NodeWithPos & {depth: number})[] { + let {depth} = $pos; + const nodes: (NodeWithPos & {depth: number})[] = []; + + while (depth >= 0) { + const node = $pos.node(depth); + if (node.type === type) nodes.push({depth, node, pos: $pos.before(depth)}); + depth--; + } + + return nodes; +}