Skip to content

Commit

Permalink
feat: support inserting images
Browse files Browse the repository at this point in the history
  • Loading branch information
sibiraj-s committed Nov 29, 2020
1 parent 5bf6731 commit 87b1414
Show file tree
Hide file tree
Showing 9 changed files with 155 additions and 12 deletions.
2 changes: 1 addition & 1 deletion demo/src/app/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const getPlugins = (): Plugin[] => {
['code', 'blockquote'],
['ordered_list', 'bullet_list'],
[{ heading: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] }],
['link'],
['link', 'image'],
[codemirrorMenu]
],
labels: {
Expand Down
5 changes: 3 additions & 2 deletions src/plugins/menu/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const DEFAULT_TOOLBAR: Toolbar = [
['code', 'blockquote'],
['ordered_list', 'bullet_list'],
[{ heading: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] }],
['link']
['link', 'image']
];

const DEFAULT_LABELS: MenuLabels = {
Expand All @@ -22,7 +22,8 @@ const DEFAULT_LABELS: MenuLabels = {
bullet_list: 'Bullet List',
heading: 'Heading',
blockquote: 'Quote',
link: 'Link'
link: 'Link',
image: 'Image'
};

const DEFAULT_OPTIONS: MenuOptions = {
Expand Down
128 changes: 128 additions & 0 deletions src/plugins/menu/items/image.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { EditorView } from 'prosemirror-view';
import { toggleMark } from 'prosemirror-commands';
import { EditorState, NodeSelection } from 'prosemirror-state';

import MenuItem from '../views/base/MenuItem';
import { MenuItemSpec, MenuItemViewRender } from '../../types';

import Popup from '../views/base/Popup';
import FormView, { FormInputs, OnSubmitData } from '../views/base/Form';

const getFormInputs = (url = '', alt = '', title = ''): FormInputs => [
[
{
type: 'url',
required: true,
label: 'URL',
name: 'url',
defaultValue: url
}
],
[
{
type: 'text',
required: false,
label: 'Alt Text',
name: 'altText',
defaultValue: alt
}
],
[
{
type: 'text',
required: false,
label: 'Title',
name: 'title',
defaultValue: title
}
]
];

const updateImage = (view: EditorView, data: OnSubmitData) => {
const { dispatch, state: { schema, tr } } = view;

const attrs = {
src: data.url,
alt: data.altText ?? '',
title: data.title ?? ''
};

dispatch(tr.replaceSelectionWith(schema.nodes.image.createAndFill(attrs)));
};

const image = (view: EditorView, spec: MenuItemSpec): MenuItemViewRender => {
const { dom, update: updateDom } = new MenuItem(spec);

const onPopupOpen = () => {
const { state: { selection } } = view;

if (selection instanceof NodeSelection) {
const { src, alt, title } = selection.node.attrs;
renderForm(getFormInputs(src, alt, title));
} else {
renderForm(getFormInputs());
}

return true;
};

const onPopupClose = () => {
return true;
};

const updateActiveState = () => {
setActiveState();
};

const popupOptions = {
menuDOM: dom,
onOpen: onPopupOpen,
afterOpen: updateActiveState,
onClose: onPopupClose,
afterClose: updateActiveState
};

const { dom: popupDOM, closePopup, isPopupOpen } = new Popup(popupOptions);

const onSubmit = (data: OnSubmitData) => {
updateImage(view, data);
closePopup();
};

const { dom: formDom, render: renderForm } = new FormView({ inputs: getFormInputs(), onSubmit });

popupDOM.appendChild(formDom);
dom.appendChild(popupDOM);

const setActiveState = () => {
updateDom({
active: isPopupOpen(),
disabled: false
});
};

const update = (state: EditorState) => {
const { schema, selection } = state;

const command = toggleMark(schema.nodes.image);
const canExecute = command(state, null);

let isActive = false;

if (selection instanceof NodeSelection) {
isActive = selection.node.type.name === 'image';
}

updateDom({
active: isPopupOpen() || isActive,
disabled: !canExecute
});
};

return {
dom,
update
};
};

export default image;
12 changes: 6 additions & 6 deletions src/plugins/menu/items/link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { MenuItemSpec, MenuItemViewRender } from '../../types';
import Popup from '../views/base/Popup';
import FormView, { FormInputs, OnSubmitData } from '../views/base/Form';

const getFormInputs = (defaultValue = '', disableText = false): FormInputs => [
const getFormInputs = (text = '', disableText = false): FormInputs => [
[
{
type: 'url',
Expand All @@ -25,7 +25,7 @@ const getFormInputs = (defaultValue = '', disableText = false): FormInputs => [
required: true,
label: 'Text',
name: 'text',
defaultValue,
defaultValue: text,
disabled: disableText
}
],
Expand Down Expand Up @@ -118,11 +118,11 @@ const link = (view: EditorView, spec: MenuItemSpec): MenuItemViewRender => {
});
};

const update = (editorState: EditorState) => {
const { schema } = editorState;
const update = (state: EditorState) => {
const { schema } = state;
const command = toggleMark(schema.marks.link);
const canExecute = command(editorState, null);
const isActive = isMarkActive(editorState, schema.marks.link);
const canExecute = command(state, null);
const isActive = isMarkActive(state, schema.marks.link);

toggleIcon(isActive);

Expand Down
4 changes: 3 additions & 1 deletion src/plugins/menu/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,16 @@ import DropDownView from './views/Dropdown';

import getSeperator from './items/seperator';
import link from './items/link';
import image from './items/image';

import menuItemsMeta from './meta';

const DROPDOWN_ITEMS = new Map();
DROPDOWN_ITEMS.set('heading', ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']);

const builtInMenuItems = {
link
link,
image
};

export const renderMenu = (options: MenuOptions, editorView: EditorView, menuDom: HTMLElement) => {
Expand Down
6 changes: 6 additions & 0 deletions src/plugins/menu/meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ const menuItemsMeta: { [key: string]: MenuItemMeta } = {
icon: 'link',
type: 'mark',
toggleIcon: 'unlink'
},
image: {
key: 'image',
i18nKey: 'image',
icon: 'image',
type: 'node'
}
};

Expand Down
2 changes: 1 addition & 1 deletion src/plugins/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { EditorView } from 'prosemirror-view';
type TCR = { dom: HTMLElement, update: (state: EditorState) => void };

type TBHeading = Array<'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'>;
type TBItems = 'bold' | 'italic' | 'code' | 'blockquote' | 'ordered_list' | 'bullet_list' | 'link';
type TBItems = 'bold' | 'italic' | 'code' | 'blockquote' | 'ordered_list' | 'bullet_list' | 'link' | 'image';

export type ToolbarDropdown = { heading?: TBHeading };
export type ToolbarBuiltInMenuItem = (editorView: EditorView, spec: MenuItemSpec) => TCR;
Expand Down
4 changes: 4 additions & 0 deletions src/plugins/utils/icons/image.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default `
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
`;
4 changes: 3 additions & 1 deletion src/plugins/utils/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import bulletList from './bullet_list';
import quote from './quote';
import link from './link';
import unlink from './unlink';
import image from './image';

const DEFAULT_ICON_HEIGHT = 20;
const DEFAULT_ICON_WIDTH = 20;
Expand All @@ -20,7 +21,8 @@ const icons = {
bullet_list: bulletList,
quote,
link,
unlink
unlink,
image
};

// Helper function to create menu icons
Expand Down

0 comments on commit 87b1414

Please sign in to comment.