silverbullet/web/cm_plugins/hide_mark.ts

129 lines
3.3 KiB
TypeScript
Raw Permalink Normal View History

// Forked from https://codeberg.org/retronav/ixora
// Original author: Pranav Karawale
// License: Apache License 2.0.
2024-07-30 23:33:33 +08:00
import type { EditorState } from "@codemirror/state";
import { syntaxTree } from "@codemirror/language";
import { Decoration } from "@codemirror/view";
import {
checkRangeOverlap,
decoratorStateField,
invisibleDecoration,
isCursorInRange,
} from "./util.ts";
/**
* These types contain markers as child elements that can be hidden.
*/
const typesWithMarks = [
"Emphasis",
"StrongEmphasis",
"InlineCode",
"Highlight",
"Strikethrough",
"Superscript",
"Subscript",
];
/**
* The elements which are used as marks.
*/
const markTypes = [
"EmphasisMark",
"CodeMark",
"HighlightMark",
"StrikethroughMark",
"SuperscriptMark",
"SubscriptMark",
];
/**
* Ixora hide marks plugin.
*
* This plugin allows to:
* - Hide marks when they are not in the editor selection.
*/
export function hideMarksPlugin() {
return decoratorStateField((state: EditorState) => {
const widgets: any[] = [];
let parentRange: [number, number];
syntaxTree(state).iterate({
enter: ({ type, from, to, node }) => {
if (typesWithMarks.includes(type.name)) {
// There can be a possibility that the current node is a
// child eg. a bold node in a emphasis node, so check
// for that or else save the node range
if (
parentRange &&
checkRangeOverlap([from, to], parentRange)
) {
return;
} else parentRange = [from, to];
if (isCursorInRange(state, [from, to])) return;
const innerTree = node.toTree();
innerTree.iterate({
enter({ type, from: markFrom, to: markTo }) {
// Check for mark types and push the replace
// decoration
if (!markTypes.includes(type.name)) return;
widgets.push(
invisibleDecoration.range(
from + markFrom,
from + markTo,
),
);
},
});
}
},
});
return Decoration.set(widgets, true);
});
}
// HEADINGS
export function hideHeaderMarkPlugin() {
return decoratorStateField((state) => {
const widgets: any[] = [];
syntaxTree(state).iterate({
enter: ({ type, from, to }) => {
if (!type.name.startsWith("ATXHeading")) {
return;
}
// Get the active line
const line = state.sliceDoc(from, to);
if (line === "#") {
// Empty header, potentially a tag, style it as such
widgets.push(
Decoration.mark({
tagName: "span",
class: "sb-hashtag-text",
}).range(from, from + 1),
);
return;
}
if (isCursorInRange(state, [from, to])) {
widgets.push(
Decoration.line({ class: "sb-header-inside" }).range(from),
);
return;
}
2022-12-27 22:22:14 +08:00
const spacePos = line.indexOf(" ");
if (spacePos === -1) {
// Not complete header
return;
}
widgets.push(
invisibleDecoration.range(
from,
2022-12-27 22:22:14 +08:00
from + spacePos + 1,
),
);
},
});
return Decoration.set(widgets, true);
});
}