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
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {builders} from 'prosemirror-test-builder';
import {createMarkupChecker} from '../../../../../tests/sameMarkup';
import {ExtensionsManager} from '../../../../core';
import {BaseNode, BaseSchemaSpecs} from '../../../base/specs';
import {ItalicSpecs, headingNodeName, italicMarkName} from '../../../markdown/specs';
import {HeadingSpecs, ItalicSpecs, headingNodeName, italicMarkName} from '../../../markdown/specs';
import {YfmHeadingAttr, YfmHeadingSpecs} from '../../../yfm/specs';

import {FoldingHeadingSpecs} from './FoldingHeadingSpecs';
Expand All @@ -13,6 +13,7 @@ const {schema, markupParser, serializer} = new ExtensionsManager({
builder
.use(BaseSchemaSpecs, {})
.use(ItalicSpecs)
.use(HeadingSpecs, {})
.use(YfmHeadingSpecs, {})
.use(FoldingHeadingSpecs),
}).buildDeps();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {nodeTypeFactory} from 'src/utils/schema';

export const headingNodeName = 'heading';
export const headingLevelAttr = 'level';
export const headingLineNumberAttr = 'data-line';
export const headingType = nodeTypeFactory(headingNodeName);
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import type {Node, NodeSpec} from 'prosemirror-model';
import type {ExtensionAuto} from '#core';
import type {Node, NodeSpec} from '#pm/model';

import type {ExtensionAuto} from '../../../../core';
import {nodeTypeFactory} from '../../../../utils/schema';
import {headingLevelAttr, headingLineNumberAttr, headingNodeName} from './const';
import {headingToMarkdown} from './utils';

export const headingNodeName = 'heading';
export const headingLevelAttr = 'level';
export const headingLineNumberAttr = 'data-line';
export const headingType = nodeTypeFactory(headingNodeName);
export * from './const';
export {headingToMarkdown} from './utils';

const DEFAULT_PLACEHOLDER = (node: Node) => 'Heading ' + node.attrs[headingLevelAttr];

Expand Down Expand Up @@ -56,10 +55,6 @@ export const HeadingSpecs: ExtensionAuto<HeadingSpecsOptions> = (builder, opts)
getAttrs: (tok) => ({[headingLevelAttr]: Number(tok.tag.slice(1))}),
},
},
toMd: (state, node) => {
state.write(state.repeat('#', node.attrs[headingLevelAttr]) + ' ');
state.renderInline(node);
state.closeBlock(node);
},
toMd: headingToMarkdown(),
}));
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type {SerializerNodeToken} from '#core';

import {headingLevelAttr} from './index';

type HeadingToMarkdownParams = {
renderMarkup?: SerializerNodeToken;
renderAttributes?: SerializerNodeToken;
};

const defaultMarkupRender: SerializerNodeToken = (state, node) =>
state.write(state.repeat('#', node.attrs[headingLevelAttr]) + ' ');

export function headingToMarkdown({
renderMarkup = defaultMarkupRender,
renderAttributes,
}: HeadingToMarkdownParams = {}): SerializerNodeToken {
return (...args) => {
const [state, node] = args;
renderMarkup(...args);
state.renderInline(node);
renderAttributes?.(...args);
state.closeBlock(node);
};
}
8 changes: 4 additions & 4 deletions packages/editor/src/extensions/markdown/Heading/actions.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import {setBlockType} from 'prosemirror-commands';
import type {NodeType} from 'prosemirror-model';

import type {ActionSpec} from '../../../core';

import {type HeadingLevel, headingLevelAttr} from './const';
import {toHeading} from './commands';
import type {HeadingLevel} from './const';
import {hasParentHeading} from './utils';

export const headingAction = (nodeType: NodeType, level: HeadingLevel): ActionSpec => {
const cmd = setBlockType(nodeType, {[headingLevelAttr]: level});
export const headingAction = (_nodeType: NodeType, level: HeadingLevel): ActionSpec => {
const cmd = toHeading(level);
return {
isActive: hasParentHeading(level),
isEnable: cmd,
Expand Down
27 changes: 25 additions & 2 deletions packages/editor/src/extensions/markdown/Heading/commands.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import type {Command} from 'prosemirror-state';
import {setBlockType} from '#pm/commands';
import type {Command} from '#pm/state';
import {findParentNodeOfType} from '#pm/utils';

import {toParagraph} from '../../base/BaseSchema';
import {headingType} from '../Heading/HeadingSpecs';
import {headingLevelAttr, headingType} from '../Heading/HeadingSpecs';

import type {HeadingLevel} from './const';

export const resetHeading: Command = (state, dispatch, view) => {
const {selection} = state;
Expand All @@ -14,3 +18,22 @@ export const resetHeading: Command = (state, dispatch, view) => {
}
return false;
};

export const toHeading =
(level: HeadingLevel): Command =>
(state, dispatch, view) => {
const attrs: Record<string, any> = {};

const parentHeading = findParentNodeOfType(headingType(state.schema))(state.selection);
if (parentHeading) {
if (parentHeading.node.attrs[headingLevelAttr] === level) {
return toParagraph(state, dispatch, view);
}

Object.assign(attrs, parentHeading.node.attrs);
}

attrs[headingLevelAttr] = level;

return setBlockType(headingType(state.schema), attrs)(state, dispatch);
};
22 changes: 9 additions & 13 deletions packages/editor/src/extensions/markdown/Heading/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import {setBlockType} from 'prosemirror-commands';

import type {Action, ExtensionAuto, Keymap} from '../../../core';
import {withLogAction} from '../../../utils/keymap';

import {HeadingSpecs, type HeadingSpecsOptions, headingType} from './HeadingSpecs';
import {headingAction} from './actions';
import {resetHeading} from './commands';
import {HeadingAction, type HeadingLevel, headingLevelAttr} from './const';
import {resetHeading, toHeading} from './commands';
import {HeadingAction} from './const';
import {headingRule} from './utils';

export {headingNodeName, headingType} from './HeadingSpecs';
Expand All @@ -25,18 +23,16 @@ export const Heading: ExtensionAuto<HeadingOptions> = (builder, opts) => {
builder.use(HeadingSpecs, opts);

builder
.addKeymap(({schema}) => {
.addKeymap(() => {
const {h1Key, h2Key, h3Key, h4Key, h5Key, h6Key} = opts ?? {};
const cmd4lvl = (level: HeadingLevel) =>
setBlockType(headingType(schema), {[headingLevelAttr]: level});

const bindings: Keymap = {Backspace: resetHeading};
if (h1Key) bindings[h1Key] = withLogAction('heading1', cmd4lvl(1));
if (h2Key) bindings[h2Key] = withLogAction('heading2', cmd4lvl(2));
if (h3Key) bindings[h3Key] = withLogAction('heading3', cmd4lvl(3));
if (h4Key) bindings[h4Key] = withLogAction('heading4', cmd4lvl(4));
if (h5Key) bindings[h5Key] = withLogAction('heading5', cmd4lvl(5));
if (h6Key) bindings[h6Key] = withLogAction('heading6', cmd4lvl(6));
if (h1Key) bindings[h1Key] = withLogAction('heading1', toHeading(1));
if (h2Key) bindings[h2Key] = withLogAction('heading2', toHeading(2));
if (h3Key) bindings[h3Key] = withLogAction('heading3', toHeading(3));
if (h4Key) bindings[h4Key] = withLogAction('heading4', toHeading(4));
if (h5Key) bindings[h5Key] = withLogAction('heading5', toHeading(5));
if (h6Key) bindings[h6Key] = withLogAction('heading6', toHeading(6));
return bindings;
})
.addInputRules(({schema}) => ({rules: [headingRule(headingType(schema), 6)]}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {parseDOM} from '../../../../tests/parse-dom';
import {createMarkupChecker} from '../../../../tests/sameMarkup';
import {ExtensionsManager} from '../../../core';
import {BaseNode, BaseSchemaSpecs} from '../../base/specs';
import {BoldSpecs, boldMarkName, headingNodeName} from '../../markdown/specs';
import {BoldSpecs, HeadingSpecs, boldMarkName, headingNodeName} from '../../markdown/specs';
import {YfmConfigsSpecs} from '../specs';

import {YfmHeadingAttr, YfmHeadingSpecs} from './YfmHeadingSpecs';
Expand All @@ -18,6 +18,7 @@ const {
extensions: (builder) =>
builder
.use(BaseSchemaSpecs, {})
.use(HeadingSpecs, {})
.use(YfmConfigsSpecs, {attrs: {allowedAttributes: ['id']}})
.use(YfmHeadingSpecs, {})
.use(BoldSpecs),
Expand Down Expand Up @@ -114,6 +115,7 @@ describe('Heading extension', () => {
builder
.use(BaseSchemaSpecs, {})
.use(BoldSpecs)
.use(HeadingSpecs, {})
.use(YfmConfigsSpecs, {disableAttrs: true})
.use(YfmHeadingSpecs, {}),
}).buildDeps();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {headingLevelAttr} from '../../../markdown/Heading/HeadingSpecs';
import {headingLevelAttr, headingLineNumberAttr} from '../../../markdown/Heading/HeadingSpecs';
export type {HeadingLevel} from '../../../markdown/Heading/const';

export {headingLevelAttr, headingNodeName} from '../../../markdown/Heading/HeadingSpecs';

export const YfmHeadingAttr = {
Level: headingLevelAttr,
DataLine: headingLineNumberAttr,
Id: 'id',
DataLine: 'data-line',
Folding: 'folded',
Folding: 'folded', // TODO: move to FoldingHeadingExtension
} as const;
Loading
Loading