Skip to content
Open
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
12 changes: 9 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
]
},
"dependencies": {
"@quartzy/markdown-it-mentions": "^0.2.0",
"copy-to-clipboard": "^3.0.8",
"fuzzy-search": "^3.2.1",
"gemoji": "6.x",
Expand Down Expand Up @@ -52,7 +53,8 @@
"refractor": "^3.3.1",
"resize-observer-polyfill": "^1.5.1",
"slugify": "^1.4.0",
"smooth-scroll-into-view-if-needed": "^1.1.29"
"smooth-scroll-into-view-if-needed": "^1.1.29",
"url-parse": "^1.5.10"
},
"peerDependencies": {
"react": "^16.0.0 || ^17.0.0",
Expand Down Expand Up @@ -112,7 +114,8 @@
"resolutions": {
"markdown-it": "^12.2.0",
"prosemirror-transform": "1.2.5",
"yargs-parser": "^15.0.1"
"yargs-parser": "^15.0.1",
"prosemirror-model": "1.9.1"
},
"repository": {
"type": "git",
Expand All @@ -128,5 +131,8 @@
"bugs": {
"url": "https://github.com/outline/rich-markdown-editor/issues"
},
"homepage": "https://github.com/outline/rich-markdown-editor#readme"
"homepage": "https://github.com/outline/rich-markdown-editor#readme",
"volta": {
"node": "16.18.0"
}
}
57 changes: 56 additions & 1 deletion src/components/CommandMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import getDataTransferFiles from "../lib/getDataTransferFiles";
import filterExcessSeparators from "../lib/filterExcessSeparators";
import insertFiles from "../commands/insertFiles";
import baseDictionary from "../dictionary";
import createAndInsertLink from "../commands/createAndInsertLink";

const SSR = typeof window === "undefined";

Expand All @@ -29,6 +30,7 @@ export type Props<T extends MenuItem = MenuItem> = {
view: EditorView;
search: string;
uploadImage?: (file: File) => Promise<string>;
onCreateLink?:(title: string) => Promise<string>;
onImageUploadStart?: () => void;
onImageUploadStop?: () => void;
onShowToast?: (message: string, id: string) => void;
Expand Down Expand Up @@ -173,7 +175,46 @@ class CommandMenu<T = MenuItem> extends React.Component<Props<T>, State> {
}
};

handleOnCreateLink = async (title: string) => {
const { dictionary, onCreateLink, view, onClose, onShowToast } = this.props;

onClose();
this.props.view.focus();

if (!onCreateLink) {
return;
}

const { dispatch, state } = view;
const { from, to } = state.selection;
if (from !== to) {
// selection must be collapsed
return;
}

const href = `creating#${title}…`;

// Insert a placeholder link
dispatch(
view.state.tr
.insertText(title, from, to)
.addMark(
from,
to + title.length,
state.schema.marks.link.create({ href })
)
);

createAndInsertLink(view, title, href, {
onCreateLink,
onShowToast,
dictionary,
});
};

insertItem = item => {
console.log(item.name);

switch (item.name) {
case "image":
return this.triggerImagePick();
Expand All @@ -185,6 +226,8 @@ class CommandMenu<T = MenuItem> extends React.Component<Props<T>, State> {
this.props.onLinkToolbarOpen?.();
return;
}
// case 'mention' :
// return this.handleOnCreateLink(item.title)
default:
this.insertBlock(item);
}
Expand Down Expand Up @@ -302,7 +345,19 @@ class CommandMenu<T = MenuItem> extends React.Component<Props<T>, State> {
this.clearSearch();

const command = this.props.commands[item.name];

console.log(item,'insertBlock');

// if(item.name == 'mention') {
// const { state, dispatch } = this.props.view;
// dispatch(
// state.tr.insertText(
// item.title,
// state.selection.$from.pos - (this.props.search ?? "").length - 1,
// state.selection.to
// )
// );
// }

if (command) {
command(item.attrs);
} else {
Expand Down
120 changes: 120 additions & 0 deletions src/components/MentionMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import React from "react";
import gemojies from "gemoji";
import FuzzySearch from "fuzzy-search";
import CommandMenu, { Props } from "./CommandMenu";
import EmojiMenuItem from "./EmojiMenuItem";
import MentionMenuItem from "./MentionMenuItem";

type Emoji = {
name: string;
title: string;
// emoji: string;
email: string;
attrs: { markup: string; "data-name": string };
};

const people = [{
name: {
firstName: 'Jesse',
lastName: 'Bowen',
},
state: 'Seattle',
},
{
name: {
firstName: 'juniad',
lastName: 'Bowen',
},
state: 'junaid',

},
{
name: {
firstName: 'juniad',
lastName: 'adam',
},
state: 'adam',
},
{
name: {
firstName: 'no-name',
lastName: 'yes-name',
},
state: 'name',
}
];

const searcher = new FuzzySearch(people, ['name.firstName', 'state'], {
caseSensitive: true,
sort: true,
});
type onCreateLink = (title: string) => Promise<void>
class MentionMenu extends React.Component<
Omit<
Props<Emoji & onCreateLink>,
| "renderMenuItem"
| "items"
| "onLinkToolbarOpen"
| "embeds"
| "onClearSearch"
>
> {
get items(): Emoji[] {
const { search = "" } = this.props;

const n = search.toLowerCase();
const result = searcher.search(n).map(item => {
const email = item.name.firstName;
const name = item.state;
return {
...item,
name: "mention",
title: name,
email,
attrs: { markup: name, "data-name": name },
};
});

return result.slice(0, 10);
}

clearSearch = () => {
const { state, dispatch } = this.props.view;

// clear search input
dispatch(
state.tr.insertText(
"",
state.selection.$from.pos - (this.props.search ?? "").length - 1,
state.selection.to
)
);
};


render() {
return (
<CommandMenu
{...this.props}
id="emoji-menu-container"
filterable={false}
onClearSearch={this.clearSearch}
onCreateLink={this.props.onCreateLink}
renderMenuItem={(item, _index, options) => {
return (
<MentionMenuItem
onClick={options.onClick}
selected={options.selected}
title={item.title}
// emoji={item.emoji}
containerId="emoji-menu-container"
/>
);
}}
items={this.items}
/>
);
}
}

export default MentionMenu;
39 changes: 39 additions & 0 deletions src/components/MentionMenuItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as React from "react";
import BlockMenuItem, { Props as BlockMenuItemProps } from "./BlockMenuItem";
import styled from "styled-components";

const Emoji = styled.span`
font-size: 16px;
`;


const MentionName = ({
title,
}: {
title: React.ReactNode;
}) => {
return (
<p>
{/* <Emoji className="emoji">{emoji}</Emoji> */}
&nbsp;&nbsp;
{title}
</p>
);
};

type EmojiMenuItemProps = Omit<BlockMenuItemProps, "shortcut" | "theme"> & {
// emoji: string;
};

const Mentions = styled(MentionName)({
cursor: 'pointer'
})

export default function MentionMenuItem(props: EmojiMenuItemProps) {
return (
<BlockMenuItem
{...props}
title={<Mentions title={props.title} />}
/>
);
}
26 changes: 26 additions & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import CodeBlock from "./nodes/CodeBlock";
import CodeFence from "./nodes/CodeFence";
import CheckboxList from "./nodes/CheckboxList";
import Emoji from "./nodes/Emoji";
import Mention from "./nodes/Mention";
import CheckboxItem from "./nodes/CheckboxItem";
import Embed from "./nodes/Embed";
import HardBreak from "./nodes/HardBreak";
Expand Down Expand Up @@ -68,6 +69,7 @@ import Underline from "./marks/Underline";
// plugins
import BlockMenuTrigger from "./plugins/BlockMenuTrigger";
import EmojiTrigger from "./plugins/EmojiTrigger";
import MentionsTrigger from "./plugins/Mentions";
import Folding from "./plugins/Folding";
import History from "./plugins/History";
import Keys from "./plugins/Keys";
Expand All @@ -77,6 +79,7 @@ import SmartText from "./plugins/SmartText";
import TrailingNode from "./plugins/TrailingNode";
import PasteHandler from "./plugins/PasteHandler";
import { PluginSimple } from "markdown-it";
import MentionMenu from "./components/MentionMenu";

export { schema, parser, serializer, renderToHtml } from "./server";

Expand Down Expand Up @@ -164,6 +167,7 @@ type State = {
linkMenuOpen: boolean;
blockMenuSearch: string;
emojiMenuOpen: boolean;
mentionsOpen: boolean
};

type Step = {
Expand Down Expand Up @@ -197,6 +201,7 @@ class RichMarkdownEditor extends React.PureComponent<Props, State> {
linkMenuOpen: false,
blockMenuSearch: "",
emojiMenuOpen: false,
mentionsOpen: false
};

isBlurred: boolean;
Expand All @@ -219,6 +224,8 @@ class RichMarkdownEditor extends React.PureComponent<Props, State> {
rulePlugins: PluginSimple[];

componentDidMount() {
console.log('hellooo');

this.init();

if (this.props.scrollTo) {
Expand Down Expand Up @@ -327,6 +334,7 @@ class RichMarkdownEditor extends React.PureComponent<Props, State> {
onShowToast: this.props.onShowToast,
}),
new Emoji(),
new Mention(),
new Text(),
new CheckboxList(),
new CheckboxItem(),
Expand Down Expand Up @@ -397,6 +405,14 @@ class RichMarkdownEditor extends React.PureComponent<Props, State> {
this.setState({ emojiMenuOpen: false });
},
}),
new MentionsTrigger({
onOpen: (search: string) => {
this.setState({ mentionsOpen: true, blockMenuSearch: search });
},
onClose: () => {
this.setState({ mentionsOpen: false });
},
}),
new Placeholder({
placeholder: this.props.placeholder,
}),
Expand Down Expand Up @@ -814,6 +830,16 @@ class RichMarkdownEditor extends React.PureComponent<Props, State> {
search={this.state.blockMenuSearch}
onClose={() => this.setState({ emojiMenuOpen: false })}
/>
<MentionMenu
view={this.view}
commands={this.commands}
dictionary={dictionary}
rtl={isRTL}
onCreateLink={this.props.onCreateLink}
isActive={this.state.mentionsOpen}
search={this.state.blockMenuSearch}
onClose={() => this.setState({ mentionsOpen: false })}
/>
<BlockMenu
view={this.view}
commands={this.commands}
Expand Down
2 changes: 2 additions & 0 deletions src/lib/renderToHtml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import tablesRule from "../rules/tables";
import noticesRule from "../rules/notices";
import underlinesRule from "../rules/underlines";
import emojiRule from "../rules/emoji";
import mention from "../rules/mentions";

const defaultRules = [
embedsRule,
breakRule,
checkboxRule,
markRule({ delim: "==", mark: "highlight" }),
markRule({ delim: "!!", mark: "placeholder" }),
mention,
underlinesRule,
tablesRule,
noticesRule,
Expand Down
Loading