Skip to content

Commit

Permalink
Inline sync plugin (#695)
Browse files Browse the repository at this point in the history
* feat: add plugin

* feat: 🎸 finish inline sync plugin

* test: 💍 fix e2e

* chore: 🤖 remove inline inputrule for math

* feat: 🎸 make it possible to apply multiple mark on single line

* fix: 🐛 e2e

* fix: 🐛 conflict with inputrules

* feat: 🎸 make it possible to input \

* feat: 🎸 move placeholder when meet special characters

* feat: 🎸 optimize code

* feat: 🎸 only move placeholder in middle

* feat: 🎸 optimize code

* feat: 🎸 remove inclusive

* feat: 🎸 remove unneed history

* refactor: 💡 refine code

* feat: 🎸 use smart placeholder

* refactor: 💡 optimize code

* test: 💍 fix e2e
  • Loading branch information
Saul-Mirone authored Aug 30, 2022
1 parent fdbcb99 commit fcd893b
Show file tree
Hide file tree
Showing 19 changed files with 213 additions and 99 deletions.
3 changes: 2 additions & 1 deletion e2e/cypress/e2e/preset-commonmark/input.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,8 @@ describe('input:', () => {
});

it('inline code with * and _', () => {
cy.get('.editor').type('The lunatic is `**_on the grass_**`');
cy.get('.editor').type('The lunatic is `****`');
cy.get('.editor').type('{leftArrow}').type('{leftArrow}').type('_on the grass_');
cy.get('.editor').get('.code-inline').should('have.text', '**_on the grass_**');
cy.window().then((win) => {
cy.wrap(win.__getMarkdown__()).snapshot();
Expand Down
10 changes: 0 additions & 10 deletions e2e/cypress/e2e/preset-commonmark/shortcut.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,6 @@ describe('input:', () => {
cy.get('.strong').should('have.text', 'The lunatic is on the grass');
cy.get('.editor').type(`{${isMac ? 'cmd' : 'ctrl'}+b}`);
cy.get('.strong').should('not.exist');
cy.get('.editor').type('{backspace}');
cy.get('.editor').type('The lunatic is ');
cy.get('.editor').type(`{${isMac ? 'cmd' : 'ctrl'}+b}`);
cy.get('.editor').type('on the grass');
cy.get('.strong').should('have.text', 'on the grass');
});

it('italic', () => {
Expand All @@ -126,11 +121,6 @@ describe('input:', () => {
cy.get('.em').should('have.text', 'The lunatic is on the grass');
cy.get('.editor').type(`{${isMac ? 'cmd' : 'ctrl'}+i}`);
cy.get('.em').should('not.exist');
cy.get('.editor').type('{backspace}');
cy.get('.editor').type('The lunatic is ');
cy.get('.editor').type(`{${isMac ? 'cmd' : 'ctrl'}+i}`);
cy.get('.editor').type('on the grass');
cy.get('.em').should('have.text', 'on the grass');
});

it('inline code', () => {
Expand Down
5 changes: 0 additions & 5 deletions e2e/cypress/e2e/preset-gfm/shortcut.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,5 @@ describe('shortcut:', () => {
cy.get('.strike-through').should('have.text', 'The lunatic is on the grass');
cy.get('.editor').type(`{${isMac ? 'cmd' : 'ctrl'}+alt+x}`);
cy.get('.strike-through').should('not.exist');
cy.get('.editor').type('{backspace}');
cy.get('.editor').type('The lunatic is ');
cy.get('.editor').type(`{${isMac ? 'cmd' : 'ctrl'}+alt+x}`);
cy.get('.editor').type('on the grass');
cy.get('.strike-through').should('have.text', 'on the grass');
});
});
6 changes: 3 additions & 3 deletions e2e/snapshots.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ module.exports = {
"ordered list": {
"1": "1. list item 1\n2. list item 2\n\n 1. sub list item 1\n 2. sub list item 2\n3. list item 3\n"
},
"hr": {
"1": "***\n"
},
"image": {
"invalid image": {
"1": "![image](invalidUrl)\n"
Expand All @@ -30,6 +27,9 @@ module.exports = {
},
"list": {
"1": "1. The lunatic is on the grass\n2. The lunatic is in the hell\n\n * The lunatic is on the grass\n * The lunatic is in the hell\n"
},
"hr": {
"1": "***\n"
}
},
"mark:": {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"test:lint": "eslint \"**/{src,website,examples}/**/*.{js,ts,tsx}\"",
"test:doc": "prettier --check \"**/*.md\"",
"test:e2e": "pnpm --filter=@milkdown/e2e run start:test",
"test:e2e:verbose": "pnpm --filter=@milkdown/e2e run start:test:verbose",
"test:e2e:cache": "nx e2e e2e",
"format": "lint-staged",
"watch": "pnpm --filter=./packages --parallel run start",
Expand Down
22 changes: 0 additions & 22 deletions packages/plugin-math/src/math-inline/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import { Color, commandsCtx, createCmd, createCmdKey, ThemeColor, ThemeInputChipType, ThemeSize } from '@milkdown/core';
import { expectDomTypeError } from '@milkdown/exception';
import { findSelectedNodeOfType } from '@milkdown/prose';
import { InputRule } from '@milkdown/prose/inputrules';
import { NodeSelection, Plugin, PluginKey } from '@milkdown/prose/state';
import { EditorView } from '@milkdown/prose/view';
import { createNode } from '@milkdown/utils';
Expand Down Expand Up @@ -150,27 +149,6 @@ export const mathInline = createNode<string, Options>((utils, options) => {
},
};
},
inputRules: (nodeType) => [
new InputRule(/(?:\$)([^$]+)(?:\$)$/, (state, match, start, end) => {
const $start = state.doc.resolve(start);
const index = $start.index();
const $end = state.doc.resolve(end);
if (!$start.parent.canReplaceWith(index, $end.index(), nodeType)) {
return null;
}
const value = match[1] ?? '';
return state.tr.replaceRangeWith(
start,
end,
nodeType.create(
{
value,
},
nodeType.schema.text(value),
),
);
}),
],
prosePlugins: (type, ctx) => {
return [
new Plugin({
Expand Down
17 changes: 6 additions & 11 deletions packages/plugin-tooltip/src/item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,7 @@ export type ButtonItem = {
enable: Pred;
};

export const createToggleIcon = (
icon: Icon,
onClick: string,
mark: MarkType | undefined,
disableForMark: MarkType | undefined,
): Item => ({
export const createToggleIcon = (icon: Icon, onClick: string, mark?: MarkType, disableForMark?: MarkType): Item => ({
icon,
onClick,
isHidden: () => (view: EditorView) => isTextAndNotHasMark(view.state, disableForMark),
Expand All @@ -44,11 +39,11 @@ export const createToggleIcon = (
export const defaultButtons = (ctx: Ctx) => {
const marks = ctx.get(schemaCtx).marks;
return [
createToggleIcon('bold', 'ToggleBold', marks['strong'], marks['code_inline']),
createToggleIcon('italic', 'ToggleItalic', marks['em'], marks['code_inline']),
createToggleIcon('strikeThrough', 'ToggleStrikeThrough', marks['strike_through'], marks['code_inline']),
createToggleIcon('code', 'ToggleInlineCode', marks['code_inline'], marks['link']),
createToggleIcon('link', 'ToggleLink', marks['link'], marks['code_inline']),
createToggleIcon('bold', 'ToggleBold', marks['strong']),
createToggleIcon('italic', 'ToggleItalic', marks['em']),
createToggleIcon('strikeThrough', 'ToggleStrikeThrough', marks['strike_through']),
createToggleIcon('code', 'ToggleInlineCode', marks['code_inline']),
createToggleIcon('link', 'ToggleLink', marks['link']),
];
};

Expand Down
2 changes: 0 additions & 2 deletions packages/preset-commonmark/src/mark/code-inline.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* Copyright 2021, Milkdown by Mirone. */
import { createCmd, createCmdKey } from '@milkdown/core';
import { markRule } from '@milkdown/prose';
import { toggleMark } from '@milkdown/prose/commands';
import { createMark, createShortcut } from '@milkdown/utils';

Expand Down Expand Up @@ -35,7 +34,6 @@ export const codeInline = createMark<Keys>((utils) => {
},
},
}),
inputRules: (markType) => [markRule(/(?:^|[^`])(`([^`]+)`)$/, markType)],
commands: (markType) => [createCmd(ToggleInlineCode, () => toggleMark(markType))],
shortcuts: {
[SupportedKeys.CodeInline]: createShortcut(ToggleInlineCode, 'Mod-e'),
Expand Down
7 changes: 1 addition & 6 deletions packages/preset-commonmark/src/mark/em.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* Copyright 2021, Milkdown by Mirone. */
import { createCmd, createCmdKey } from '@milkdown/core';
import { markRule } from '@milkdown/prose';
import { toggleMark } from '@milkdown/prose/commands';
import { createMark, createShortcut } from '@milkdown/utils';

Expand All @@ -11,10 +10,10 @@ type Keys = SupportedKeys['Em'];
const id = 'em';

export const ToggleItalic = createCmdKey('ToggleItalic');

export const em = createMark<Keys>((utils) => ({
id,
schema: () => ({
inclusive: false,
parseDOM: [
{ tag: 'i' },
{ tag: 'em' },
Expand All @@ -36,10 +35,6 @@ export const em = createMark<Keys>((utils) => ({
},
},
}),
inputRules: (markType) => [
markRule(/(?:^|\s|[^\w`*_])((?:_)([^_]+)(?:_))$/, markType),
markRule(/(?:^|\s|[^\w`*_])((?:\*)([^*]+)(?:\*))$/, markType),
],
commands: (markType) => [createCmd(ToggleItalic, () => toggleMark(markType))],
shortcuts: {
[SupportedKeys.Em]: createShortcut(ToggleItalic, 'Mod-i'),
Expand Down
21 changes: 2 additions & 19 deletions packages/preset-commonmark/src/mark/link.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
/* Copyright 2021, Milkdown by Mirone. */
import { commandsCtx, createCmd, createCmdKey, schemaCtx, ThemeInputChipType } from '@milkdown/core';
import { commandsCtx, createCmd, createCmdKey, ThemeInputChipType } from '@milkdown/core';
import { expectDomTypeError, missingRootElement } from '@milkdown/exception';
import { calculateTextPosition } from '@milkdown/prose';
import { toggleMark } from '@milkdown/prose/commands';
import { InputRule } from '@milkdown/prose/inputrules';
import { Node as ProseNode } from '@milkdown/prose/model';
import { NodeSelection, Plugin, PluginKey, TextSelection } from '@milkdown/prose/state';
import { EditorView } from '@milkdown/prose/view';
Expand All @@ -24,11 +23,11 @@ export const link = createMark<string, LinkOptions>((utils, options) => {
return {
id,
schema: () => ({
inclusive: false,
attrs: {
href: {},
title: { default: null },
},
inclusive: false,
parseDOM: [
{
tag: 'a[href]',
Expand Down Expand Up @@ -101,22 +100,6 @@ export const link = createMark<string, LinkOptions>((utils, options) => {
return true;
}),
],
inputRules: (markType, ctx) => [
new InputRule(/\[(?<text>.*?)]\((?<href>.*?)(?="|\))"?(?<title>[^"]+)?"?\)/, (state, match, start, end) => {
const [okay, text = '', href, title] = match;
const { tr } = state;
if (okay) {
const content = text || 'link';
tr.replaceWith(start, end, ctx.get(schemaCtx).text(content)).addMark(
start,
content.length + start,
markType.create({ title, href }),
);
}

return tr;
}),
],
prosePlugins: (type, ctx) => {
let renderOnTop = false;
return [
Expand Down
6 changes: 1 addition & 5 deletions packages/preset-commonmark/src/mark/strong.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* Copyright 2021, Milkdown by Mirone. */
import { createCmd, createCmdKey } from '@milkdown/core';
import { markRule } from '@milkdown/prose';
import { toggleMark } from '@milkdown/prose/commands';
import { createMark, createShortcut } from '@milkdown/utils';

Expand All @@ -13,6 +12,7 @@ export const strong = createMark<Keys>((utils) => {
return {
id,
schema: () => ({
inclusive: false,
parseDOM: [
{ tag: 'b' },
{ tag: 'strong' },
Expand All @@ -34,10 +34,6 @@ export const strong = createMark<Keys>((utils) => {
},
},
}),
inputRules: (markType) => [
markRule(/(?:^|\s|[^\w`])((?:__)(\w[^_]*)(?:__))$/, markType),
markRule(/(?:^|\s|[^\w`])((?:\*\*)(\w[^*]*)(?:\*\*))$/, markType),
],
commands: (markType) => [createCmd(ToggleBold, () => toggleMark(markType))],
shortcuts: {
[SupportedKeys.Bold]: createShortcut(ToggleBold, 'Mod-b'),
Expand Down
10 changes: 7 additions & 3 deletions packages/preset-commonmark/src/node/heading.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ const createId = (node: Node) =>
const headingIdPlugin = (ctx: Ctx, type: NodeType, getId: (node: Node) => string): Plugin => {
let lock = false;
const walkThrough = (state: EditorState, callback: (tr: Transaction) => void) => {
const tr = state.tr;
const tr = state.tr.setMeta('addToHistory', false);
let found = false;
state.doc.descendants((node, pos) => {
if (node.type === type && !lock) {
if (node.textContent.trim().length === 0) {
Expand All @@ -50,14 +51,17 @@ const headingIdPlugin = (ctx: Ctx, type: NodeType, getId: (node: Node) => string
const id = getId(node);

if (attrs['id'] !== id) {
found = true;
tr.setMeta(headingIdPluginKey, true).setNodeMarkup(pos, undefined, {
...attrs,
id,
});
}
}
});
callback(tr);
if (found) {
callback(tr);
}
};
return new Plugin({
key: headingIdPluginKey,
Expand Down Expand Up @@ -93,7 +97,7 @@ const headingIdPlugin = (ctx: Ctx, type: NodeType, getId: (node: Node) => string
},
view: (view) => {
const doc = view.state.doc;
let tr = view.state.tr;
let tr = view.state.tr.setMeta('addToHistory', false);
doc.descendants((node, pos) => {
if (node.type.name === 'heading' && node.attrs['level']) {
if (!node.attrs['id']) {
Expand Down
3 changes: 2 additions & 1 deletion packages/preset-commonmark/src/plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import links from 'remark-inline-links';
import { addOrderInList } from './add-order-in-list';
import { filterHTMLPlugin } from './filter-html';
import { getInlineNodesCursorPlugin } from './inline-nodes-cursor';
import { getInlineSyncPlugin } from './inline-sync';

export const commonmarkPlugins = [
createPlugin(() => ({
prosePlugins: () => [getInlineNodesCursorPlugin()],
prosePlugins: (_, ctx) => [getInlineNodesCursorPlugin(), getInlineSyncPlugin(ctx)],
remarkPlugins: () => [links, filterHTMLPlugin, addOrderInList],
}))(),
];
Loading

1 comment on commit fcd893b

@vercel
Copy link

@vercel vercel bot commented on fcd893b Aug 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.