💄 support horizontal rules
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { EditorView, Decoration, type DecorationSet, hoverTooltip, keymap } from '@codemirror/view'
|
import { EditorView, Decoration, type DecorationSet, hoverTooltip, keymap, ViewPlugin, WidgetType, type ViewUpdate } from '@codemirror/view'
|
||||||
import { EditorState, EditorSelection, StateField, StateEffect, Annotation, RangeSetBuilder, Compartment, Transaction } from '@codemirror/state'
|
import { EditorState, EditorSelection, StateField, StateEffect, Annotation, RangeSetBuilder, Compartment, Transaction } from '@codemirror/state'
|
||||||
import { markdown } from '@codemirror/lang-markdown'
|
import { markdown } from '@codemirror/lang-markdown'
|
||||||
import { syntaxHighlighting, defaultHighlightStyle } from '@codemirror/language'
|
import { syntaxHighlighting, defaultHighlightStyle, syntaxTree } from '@codemirror/language'
|
||||||
import { history, defaultKeymap, historyKeymap, invertedEffects, selectAll, indentLess } from '@codemirror/commands'
|
import { history, defaultKeymap, historyKeymap, invertedEffects, selectAll, indentLess } from '@codemirror/commands'
|
||||||
import { search, searchKeymap, openSearchPanel } from '@codemirror/search'
|
import { search, searchKeymap, openSearchPanel } from '@codemirror/search'
|
||||||
import { marked } from 'marked'
|
import { marked } from 'marked'
|
||||||
@@ -359,6 +359,41 @@ const annotationHistory = invertedEffects.of(tr => {
|
|||||||
|
|
||||||
const themeCompartment = new Compartment()
|
const themeCompartment = new Compartment()
|
||||||
|
|
||||||
|
// Horizontal rule widget — replaces `---` lines with a visual scene-break line
|
||||||
|
class HrWidget extends WidgetType {
|
||||||
|
toDOM(): HTMLElement {
|
||||||
|
const el = document.createElement('span')
|
||||||
|
el.className = 'cm-hr-widget'
|
||||||
|
return el
|
||||||
|
}
|
||||||
|
ignoreEvent() { return false }
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildHrDecos(view: EditorView): DecorationSet {
|
||||||
|
const builder = new RangeSetBuilder<Decoration>()
|
||||||
|
syntaxTree(view.state).iterate({
|
||||||
|
enter(node) {
|
||||||
|
if (node.name === 'HorizontalRule') {
|
||||||
|
builder.add(node.from, node.to, Decoration.replace({ widget: new HrWidget() }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return builder.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
const hrPlugin = ViewPlugin.fromClass(
|
||||||
|
class {
|
||||||
|
decorations: DecorationSet
|
||||||
|
constructor(view: EditorView) { this.decorations = buildHrDecos(view) }
|
||||||
|
update(update: ViewUpdate) {
|
||||||
|
if (update.docChanged || update.viewportChanged) {
|
||||||
|
this.decorations = buildHrDecos(update.view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ decorations: v => v.decorations }
|
||||||
|
)
|
||||||
|
|
||||||
function buildTheme(fontSize: number, dark: boolean): ReturnType<typeof EditorView.theme> {
|
function buildTheme(fontSize: number, dark: boolean): ReturnType<typeof EditorView.theme> {
|
||||||
return EditorView.theme(
|
return EditorView.theme(
|
||||||
{
|
{
|
||||||
@@ -393,6 +428,14 @@ function buildTheme(fontSize: number, dark: boolean): ReturnType<typeof EditorVi
|
|||||||
'.tok-heading2': { fontSize: '1.2em' },
|
'.tok-heading2': { fontSize: '1.2em' },
|
||||||
'.tok-emphasis': { fontStyle: 'italic' },
|
'.tok-emphasis': { fontStyle: 'italic' },
|
||||||
'.tok-strong': { fontWeight: '700' },
|
'.tok-strong': { fontWeight: '700' },
|
||||||
|
'.cm-hr-widget': {
|
||||||
|
display: 'inline-block',
|
||||||
|
width: '100%',
|
||||||
|
height: '0',
|
||||||
|
borderTop: '1px solid var(--border)',
|
||||||
|
opacity: '0.45',
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
},
|
||||||
|
|
||||||
// Annotation highlight styles
|
// Annotation highlight styles
|
||||||
'.annotation-passive_voice': {
|
'.annotation-passive_voice': {
|
||||||
@@ -598,12 +641,13 @@ export function MarkdownEditor(): JSX.Element {
|
|||||||
...defaultKeymap,
|
...defaultKeymap,
|
||||||
...historyKeymap
|
...historyKeymap
|
||||||
]),
|
]),
|
||||||
markdown(),
|
markdown({ extensions: [{ remove: ['SetextHeading'] }] }),
|
||||||
syntaxHighlighting(defaultHighlightStyle),
|
syntaxHighlighting(defaultHighlightStyle),
|
||||||
rawAnnotationsField,
|
rawAnnotationsField,
|
||||||
annotationField,
|
annotationField,
|
||||||
annotationHoverTooltip,
|
annotationHoverTooltip,
|
||||||
annotationHistory,
|
annotationHistory,
|
||||||
|
hrPlugin,
|
||||||
themeCompartment.of(buildTheme(fontSize, theme === 'dark')),
|
themeCompartment.of(buildTheme(fontSize, theme === 'dark')),
|
||||||
EditorView.lineWrapping,
|
EditorView.lineWrapping,
|
||||||
EditorView.updateListener.of((update) => {
|
EditorView.updateListener.of((update) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user