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 @@ -27,7 +27,7 @@ export const WToolbarColors: React.FC<WToolbarColorsProps> = ({
enable={enabled}
currentColor={currentColor}
exec={(color) => {
action.run({color: color === currentColor ? '' : color});
action.run({color});
}}
disablePortal={disablePortal}
className={className}
Expand Down
9 changes: 5 additions & 4 deletions packages/editor/src/extensions/markdown/Bold/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {toggleMark} from 'prosemirror-commands';

import type {Action, ExtensionAuto} from '../../../core';
import {createMarkdownInlineMarkAction} from '../../../utils/actions';
import {
createMarkdownInlineMarkAction,
createMarkdownInlineMarkCommand,
} from '../../../utils/actions';
import {markInputRule} from '../../../utils/inputrules';
import {withLogAction} from '../../../utils/keymap';

Expand All @@ -21,7 +22,7 @@ export const Bold: ExtensionAuto<BoldOptions> = (builder, opts) => {
if (opts?.boldKey) {
const {boldKey} = opts;
builder.addKeymap(({schema}) => ({
[boldKey]: withLogAction('bold', toggleMark(boldType(schema))),
[boldKey]: withLogAction('bold', createMarkdownInlineMarkCommand(boldType(schema))),
}));
}

Expand Down
11 changes: 8 additions & 3 deletions packages/editor/src/extensions/markdown/Code/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import codemark from 'prosemirror-codemark';
import {toggleMark} from 'prosemirror-commands';
import {Plugin} from 'prosemirror-state';

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

import {CodeSpecs, codeType} from './CodeSpecs';
Expand All @@ -24,7 +26,10 @@ export const Code: ExtensionAuto<CodeOptions> = (builder, opts) => {
if (opts?.codeKey) {
const {codeKey} = opts;
builder.addKeymap(({schema}) => ({
[codeKey]: withLogAction('code_inline', toggleMark(codeType(schema))),
[codeKey]: withLogAction(
'code_inline',
createMarkdownInlineMarkCommand(codeType(schema)),
),
}));
}

Expand Down
12 changes: 8 additions & 4 deletions packages/editor/src/extensions/markdown/Italic/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {toggleMark} from 'prosemirror-commands';

import type {Action, ExtensionAuto} from '../../../core';
import {createMarkdownInlineMarkAction} from '../../../utils/actions';
import {
createMarkdownInlineMarkAction,
createMarkdownInlineMarkCommand,
} from '../../../utils/actions';
import {markInputRule} from '../../../utils/inputrules';
import {withLogAction} from '../../../utils/keymap';

Expand Down Expand Up @@ -29,7 +30,10 @@ export const Italic: ExtensionAuto<ItalicOptions> = (builder, opts) => {
if (opts?.italicKey) {
const {italicKey} = opts;
builder.addKeymap(({schema}) => ({
[italicKey]: withLogAction('italic', toggleMark(italicType(schema))),
[italicKey]: withLogAction(
'italic',
createMarkdownInlineMarkCommand(italicType(schema)),
),
}));
}
};
Expand Down
12 changes: 8 additions & 4 deletions packages/editor/src/extensions/markdown/Strike/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {toggleMark} from 'prosemirror-commands';

import type {Action, ExtensionAuto} from '../../../core';
import {createMarkdownInlineMarkAction} from '../../../utils/actions';
import {
createMarkdownInlineMarkAction,
createMarkdownInlineMarkCommand,
} from '../../../utils/actions';
import {markInputRule} from '../../../utils/inputrules';
import {withLogAction} from '../../../utils/keymap';

Expand All @@ -28,7 +29,10 @@ export const Strike: ExtensionAuto<StrikeOptions> = (builder, opts) => {
if (opts?.strikeKey) {
const {strikeKey} = opts;
builder.addKeymap(({schema}) => ({
[strikeKey]: withLogAction('strike', toggleMark(strikeType(schema))),
[strikeKey]: withLogAction(
'strike',
createMarkdownInlineMarkCommand(strikeType(schema)),
),
}));
}
};
Expand Down
12 changes: 8 additions & 4 deletions packages/editor/src/extensions/markdown/Underline/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {toggleMark} from 'prosemirror-commands';

import type {Action, ExtensionAuto} from '../../../core';
import {createMarkdownInlineMarkAction} from '../../../utils/actions';
import {
createMarkdownInlineMarkAction,
createMarkdownInlineMarkCommand,
} from '../../../utils/actions';
import {markInputRule} from '../../../utils/inputrules';
import {withLogAction} from '../../../utils/keymap';

Expand All @@ -28,7 +29,10 @@ export const Underline: ExtensionAuto<UnderlineOptions> = (builder, opts) => {
if (opts?.underlineKey) {
const {underlineKey} = opts;
builder.addKeymap(({schema}) => ({
[underlineKey]: withLogAction('underline', toggleMark(underlineType(schema))),
[underlineKey]: withLogAction(
'underline',
createMarkdownInlineMarkCommand(underlineType(schema)),
),
}));
}
};
Expand Down
131 changes: 131 additions & 0 deletions packages/editor/src/extensions/yfm/Color/Color.action.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import type {MarkType} from 'prosemirror-model';
import {EditorState, TextSelection} from 'prosemirror-state';

import {ExtensionsManager} from '../../../core';
import type {ActionSpec} from '../../../core/types/actions';
import {BaseSchemaSpecs} from '../../base/specs';

import {colorMarkName, colorType} from './ColorSpecs';
import {colorAction} from './const';

import {Color} from './index';

const {schema, rawActions} = new ExtensionsManager({
extensions: (builder) => builder.use(BaseSchemaSpecs, {}).use(Color),
}).build();

const action: ActionSpec = rawActions[colorAction];
const color: MarkType = colorType(schema);
const isActive = action.isActive as (state: EditorState) => boolean;
const meta = action.meta as (state: EditorState) => string | undefined;

type Segment = string | {text: string; color: string};

function makeState(segments: Segment[], from: number, to: number): EditorState {
const paragraph = schema.nodes.paragraph;
const nodes = segments.map((segment) =>
typeof segment === 'string'
? schema.text(segment)
: schema.text(segment.text, [color.create({[colorMarkName]: segment.color})]),
);
const doc = schema.node('doc', null, [paragraph.create(null, nodes)]);
const selection = TextSelection.create(doc, from + 1, to + 1);
return EditorState.create({doc, selection});
}

function run(state: EditorState, attrs?: {color?: string}) {
const ref = {state};
action.run(
ref.state,
(tr) => {
ref.state = ref.state.apply(tr);
},
undefined as never,
attrs,
);
return ref.state;
}

function colorValues(state: EditorState) {
const values: Array<string | undefined> = [];
state.doc.descendants((node) => {
if (node.isText) {
values.push(color.isInSet(node.marks)?.attrs[colorMarkName]);
}
return true;
});
return values;
}

function storedColor(state: EditorState) {
return color.isInSet(state.storedMarks ?? [])?.attrs[colorMarkName];
}

describe('Color action', () => {
it('adds a stored color mark at the cursor', () => {
const next = run(makeState(['hello'], 2, 2), {color: 'red'});

expect(storedColor(next)).toBe('red');
});

it('removes the stored color when the same color is chosen at the cursor', () => {
const base = makeState(['hello'], 2, 2);
const withStored = base.apply(
base.tr.addStoredMark(color.create({[colorMarkName]: 'red'})),
);

expect(storedColor(run(withStored, {color: 'red'}))).toBeUndefined();
});

it('replaces the stored color when a different color is chosen at the cursor', () => {
const base = makeState(['hello'], 2, 2);
const withStored = base.apply(
base.tr.addStoredMark(color.create({[colorMarkName]: 'blue'})),
);

expect(storedColor(run(withStored, {color: 'red'}))).toBe('red');
});

it('applies the chosen color to the whole mixed selection', () => {
const next = run(makeState([{text: 'AB', color: 'red'}, 'CD'], 0, 4), {color: 'red'});

expect(colorValues(next)).toEqual(['red']);
});

it('removes the color from a fully covered selection', () => {
const next = run(makeState([{text: 'ABC', color: 'red'}], 0, 3), {color: 'red'});

expect(colorValues(next)).toEqual([undefined]);
});

it('removes the color from a fully covered selection without coloring trailing whitespace', () => {
const next = run(makeState([{text: 'ABC', color: 'red'}, ' '], 0, 4), {color: 'red'});

expect(colorValues(next)).toEqual([undefined]);
});

it('replaces a fully covered selection with a different color', () => {
const next = run(makeState([{text: 'ABC', color: 'blue'}], 0, 3), {color: 'red'});

expect(colorValues(next)).toEqual(['red']);
});

it('replaces the color without extending it to trailing whitespace', () => {
const next = run(makeState([{text: 'ABC', color: 'blue'}, ' '], 0, 4), {color: 'red'});

expect(colorValues(next)).toEqual(['red', undefined]);
});

it('exposes stored-mark state through isActive and meta', () => {
const next = run(makeState(['hello'], 2, 2), {color: 'red'});

expect(isActive(next)).toBe(true);
expect(meta(next)).toBe('red');
});

it('keeps partially colored selections active', () => {
const state = makeState([{text: 'AB', color: 'red'}, 'CD'], 0, 4);

expect(isActive(state)).toBe(true);
});
});
Loading
Loading