diff --git a/demo/components/Playground.tsx b/demo/components/Playground.tsx index b6c5708f..19815475 100644 --- a/demo/components/Playground.tsx +++ b/demo/components/Playground.tsx @@ -18,7 +18,6 @@ import { type UseMarkdownEditorProps, WysiwygPlaceholderOptions, logger, - markupToolbarConfigs, useMarkdownEditor, wysiwygToolbarConfigs, } from '../../src'; @@ -29,8 +28,8 @@ import {Math} from '../../src/extensions/additional/Math'; import {Mermaid} from '../../src/extensions/additional/Mermaid'; import {YfmHtmlBlock} from '../../src/extensions/additional/YfmHtmlBlock'; import {getSanitizeYfmHtmlBlock} from '../../src/extensions/additional/YfmHtmlBlock/utils'; -import {cloneDeep} from '../../src/lodash'; import type {CodeEditor} from '../../src/markup'; +import {ToolbarsPreset} from '../../src/modules/toolbars/types'; import {VERSION} from '../../src/version'; import {getPlugins} from '../defaults/md-plugins'; import useYfmHtmlBlockStyles from '../hooks/useYfmHtmlBlockStyles'; @@ -52,19 +51,6 @@ const fileUploadHandler: FileUploadHandler = async (file) => { return {url: URL.createObjectURL(file)}; }; -const mToolbarConfig = [ - ...markupToolbarConfigs.mToolbarConfig, - [markupToolbarConfigs.mMermaidButton, markupToolbarConfigs.mYfmHtmlBlockButton], -]; -mToolbarConfig[2].push(markupToolbarConfigs.mMathListItem); - -const wToolbarConfig = cloneDeep(wysiwygToolbarConfigs.wToolbarConfig); -wToolbarConfig[2].push(wysiwygToolbarConfigs.wMathListItem); -wToolbarConfig.push([ - wysiwygToolbarConfigs.wMermaidItemData, - wysiwygToolbarConfigs.wYfmHtmlBlockItemData, -]); - const wCommandMenuConfig = wysiwygToolbarConfigs.wCommandMenuConfig.concat( wysiwygToolbarConfigs.wMathInlineItemData, wysiwygToolbarConfigs.wMathBlockItemData, @@ -92,6 +78,7 @@ export type PlaygroundProps = { escapeConfig?: EscapeConfig; wysiwygCommandMenuConfig?: wysiwygToolbarConfigs.WToolbarItemData[]; markupToolbarConfig?: ToolbarGroupData[]; + toolbarsPreset?: ToolbarsPreset; onChangeEditorType?: (mode: MarkdownEditorMode) => void; onChangeSplitModeEnabled?: (splitModeEnabled: boolean) => void; directiveSyntax?: DirectiveSyntaxValue; @@ -137,6 +124,7 @@ export const Playground = React.memo((props) => { height, extraExtensions, extensionOptions, + toolbarsPreset, wysiwygToolbarConfig, wysiwygCommandMenuConfig, markupConfigExtensions, @@ -175,6 +163,47 @@ export const Playground = React.memo((props) => { const mdEditor = useMarkdownEditor( { + preset: 'full', + wysiwygConfig: { + escapeConfig, + placeholderOptions: placeholderOptions, + extensions: (builder) => { + builder + .use(Math, { + loadRuntimeScript: () => { + import( + /* webpackChunkName: "latex-runtime" */ '@diplodoc/latex-extension/runtime' + ); + import( + // @ts-expect-error // no types for styles + /* webpackChunkName: "latex-styles" */ '@diplodoc/latex-extension/runtime/styles' + ); + }, + }) + .use(Mermaid, { + loadRuntimeScript: () => { + import( + /* webpackChunkName: "mermaid-runtime" */ '@diplodoc/mermaid-extension/runtime' + ); + }, + }) + .use(FoldingHeading) + .use(YfmHtmlBlock, { + useConfig: useYfmHtmlBlockStyles, + sanitize: getSanitizeYfmHtmlBlock({options: defaultOptions}), + head: ` + + ((props) => { initialSplitModeEnabled: initialSplitModeEnabled, initialToolbarVisible: true, splitMode: splitModeOrientation, - escapeConfig: escapeConfig, needToSetDimensionsForUploadedImages, renderPreview: renderPreviewDefined ? renderPreview : undefined, fileUploadHandler, - wysiwygConfig: { - placeholderOptions: placeholderOptions, - }, experimental: { ...experimental, directiveSyntax, @@ -209,42 +234,6 @@ export const Playground = React.memo((props) => { extensions: markupConfigExtensions, parseInsertedUrlAsImage, }, - extraExtensions: (builder) => { - builder - .use(Math, { - loadRuntimeScript: () => { - import( - /* webpackChunkName: "latex-runtime" */ '@diplodoc/latex-extension/runtime' - ); - import( - // @ts-expect-error // no types for styles - /* webpackChunkName: "latex-styles" */ '@diplodoc/latex-extension/runtime/styles' - ); - }, - }) - .use(Mermaid, { - loadRuntimeScript: () => { - import( - /* webpackChunkName: "mermaid-runtime" */ '@diplodoc/mermaid-extension/runtime' - ); - }, - }) - .use(FoldingHeading) - .use(YfmHtmlBlock, { - useConfig: useYfmHtmlBlockStyles, - sanitize: getSanitizeYfmHtmlBlock({options: defaultOptions}), - head: ` - - ((props) => { toaster={toaster} className={b('editor-view')} stickyToolbar={Boolean(stickyToolbar)} - wysiwygToolbarConfig={wysiwygToolbarConfig ?? wToolbarConfig} - markupToolbarConfig={markupToolbarConfig ?? mToolbarConfig} + toolbarsPreset={toolbarsPreset} + wysiwygToolbarConfig={wysiwygToolbarConfig} + markupToolbarConfig={markupToolbarConfig} settingsVisible={settingsVisible} editor={mdEditor} enableSubmitInPreview={enableSubmitInPreview} diff --git a/demo/stories/presets/Preset.tsx b/demo/stories/presets/Preset.tsx index 58fc5c14..57477476 100644 --- a/demo/stories/presets/Preset.tsx +++ b/demo/stories/presets/Preset.tsx @@ -11,6 +11,7 @@ import { logger, useMarkdownEditor, } from '../../../src'; +import {ToolbarsPreset} from '../../../src/modules/toolbars/types'; import type {FileUploadHandler} from '../../../src/utils/upload'; import {VERSION} from '../../../src/version'; // --- @@ -41,6 +42,7 @@ export type PresetDemoProps = { splitModeOrientation?: 'horizontal' | 'vertical' | false; stickyToolbar?: boolean; height?: CSSProperties['height']; + toolbarsPreset?: ToolbarsPreset; }; logger.setLogger({ @@ -60,6 +62,7 @@ export const Preset = React.memo((props) => { splitModeOrientation, stickyToolbar, height, + toolbarsPreset, } = props; const [editorMode, setEditorMode] = React.useState('wysiwyg'); const [mdRaw, setMdRaw] = React.useState(''); @@ -130,6 +133,7 @@ export const Preset = React.memo((props) => {
= { @@ -22,6 +40,50 @@ export const Full: StoryObj = { args: {preset: 'full'}, }; +export const Custom: StoryObj = { + args: { + toolbarsPreset: { + items: { + [Action.undo]: { + view: undoItemView, + wysiwyg: undoItemWysiwyg, + markup: undoItemMarkup, + }, + [Action.redo]: { + view: redoItemView, + wysiwyg: redoItemWysiwyg, + markup: redoItemMarkup, + }, + [Action.bold]: { + view: boldItemView, + wysiwyg: boldItemWysiwyg, + }, + [Action.italic]: { + view: italicItemView, + markup: italicItemMarkup, + }, + [Action.colorify]: { + view: colorifyItemView, + wysiwyg: colorifyItemWysiwyg, + markup: colorifyItemMarkup, + }, + }, + orders: { + [Toolbar.wysiwygMain]: [ + [Action.colorify], + [Action.bold], + [Action.undo, Action.redo], + ], + [Toolbar.markupMain]: [ + [Action.colorify], + [Action.italic], + [Action.undo, Action.redo], + ], + }, + }, + }, +}; + export default { component, title: 'Extensions / Presets', diff --git a/src/bundle/MarkdownEditorView.tsx b/src/bundle/MarkdownEditorView.tsx index 437cd2de..d0fe9d6f 100644 --- a/src/bundle/MarkdownEditorView.tsx +++ b/src/bundle/MarkdownEditorView.tsx @@ -7,6 +7,7 @@ import {useEnsuredForwardedRef, useKey, useUpdate} from 'react-use'; import {ClassNameProps, cn} from '../classname'; import {i18n} from '../i18n/bundle'; import {logger} from '../logger'; +import type {ToolbarsPreset} from '../modules/toolbars/types'; import {ToasterContext, useBooleanState, useSticky} from '../react-utils'; import {isMac} from '../utils'; @@ -15,19 +16,11 @@ import {HorizontalDrag} from './HorizontalDrag'; import {MarkupEditorView} from './MarkupEditorView'; import {SplitModeView} from './SplitModeView'; import {WysiwygEditorView} from './WysiwygEditorView'; -import { - MToolbarData, - MToolbarItemData, - WToolbarData, - WToolbarItemData, - mHiddenDataByPreset, - mToolbarConfigByPreset, - wHiddenDataByPreset, - wToolbarConfigByPreset, -} from './config'; +import {MToolbarData, MToolbarItemData, WToolbarData, WToolbarItemData} from './config'; import {useMarkdownEditorContext} from './context'; import {EditorSettings, EditorSettingsProps} from './settings'; import {stickyCn} from './sticky'; +import {getToolbarsConfigs} from './toolbar/utils'; import type {MarkdownEditorMode} from './types'; import '../styles/styles.scss'; @@ -39,9 +32,22 @@ const b = cnEditorComponent; export type MarkdownEditorViewProps = ClassNameProps & { editor?: Editor; autofocus?: boolean; + toolbarsPreset?: ToolbarsPreset; + /** + * @deprecated use `toolbarsPreset` instead + */ markupToolbarConfig?: MToolbarData; + /** + * @deprecated use `toolbarsPreset` instead + */ wysiwygToolbarConfig?: WToolbarData; + /** + * @deprecated use `toolbarsPreset` instead + */ markupHiddenActionsConfig?: MToolbarItemData[]; + /** + * @deprecated use `toolbarsPreset` instead + */ wysiwygHiddenActionsConfig?: WToolbarItemData[]; /** @default true */ settingsVisible?: boolean; @@ -73,16 +79,44 @@ export const MarkdownEditorView = React.forwardRef + getToolbarsConfigs({ + toolbarsPreset, + props: { + wysiwygToolbarConfig: initialWysiwygToolbarConfig, + markupToolbarConfig: initialMarkupToolbarConfig, + wysiwygHiddenActionsConfig: initialWysiwygHiddenActionsConfig, + markupHiddenActionsConfig: initialMarkupHiddenActionsConfig, + }, + preset: editor.preset, + }), + [ + toolbarsPreset, + initialWysiwygToolbarConfig, + initialMarkupToolbarConfig, + initialWysiwygHiddenActionsConfig, + initialMarkupHiddenActionsConfig, + editor.preset, + ], + ); + const rerender = useUpdate(); React.useLayoutEffect(() => { editor.on('rerender', rerender); diff --git a/src/bundle/config/action-names.ts b/src/bundle/config/action-names.ts index 29e127ae..a4a5d2e1 100644 --- a/src/bundle/config/action-names.ts +++ b/src/bundle/config/action-names.ts @@ -1,44 +1,67 @@ const names = [ - 'undo', - 'redo', + 'anchor', 'bold', - 'italic', - 'underline', - 'strike', - 'mono', - 'mark', - 'paragraph', + 'bulletList', + 'checkbox', + /** @deprecated use codeBlock */ + 'code_block', + 'codeBlock', + /** @deprecated use codeInline */ + 'code_inline', + 'codeInline', + 'colorify', + 'emoji', + 'file', + 'filePopup', + 'gpt', 'heading1', 'heading2', 'heading3', 'heading4', 'heading5', 'heading6', - 'bulletList', - 'orderedList', + /** @deprecated use horizontalRule */ + 'horizontalrule', + 'horizontalRule', + 'image', + 'imagePopup', + 'italic', 'liftListItem', - 'sinkListItem', - 'checkbox', 'link', + 'mark', + /** @deprecated use mathBlock */ + 'math_block', + 'mathBlock', + /** @deprecated use mathInline */ + 'math_inline', + 'mathInline', + 'mermaid', + 'mono', + 'orderedList', + 'paragraph', 'quote', - 'yfm_cut', - 'yfm_note', + 'redo', + 'sinkListItem', + 'strike', + 'table', + 'tabs', + 'underline', + 'undo', + /** @deprecated use block */ 'yfm_block', + 'block', + /** @deprecated use cut */ + 'yfm_cut', + 'cut', + /** @deprecated use htmlBlock */ 'yfm_html_block', + 'htmlBlock', + /** @deprecated use layout */ 'yfm_layout', - 'table', - 'code_inline', - 'code_block', - 'image', - 'horizontalrule', - 'emoji', - 'file', - 'anchor', - 'math_inline', - 'math_block', - 'tabs', - 'mermaid', - 'gpt', + 'layout', + /** @deprecated use note */ + 'yfm_note', + 'note', ] as const; type ItemsType = L extends readonly (infer T)[] ? T : never; diff --git a/src/bundle/config/index.ts b/src/bundle/config/index.ts index aa50e1d1..5b7fbe26 100644 --- a/src/bundle/config/index.ts +++ b/src/bundle/config/index.ts @@ -1,2 +1,5 @@ +/** + * @deprecated This file is deprecated. Use ToolbarsPreset instead. + */ export * from './wysiwyg'; export * from './markup'; diff --git a/src/bundle/config/markup.tsx b/src/bundle/config/markup.tsx index 38dcbb17..b6835be9 100644 --- a/src/bundle/config/markup.tsx +++ b/src/bundle/config/markup.tsx @@ -1,3 +1,6 @@ +/** + * @deprecated This file is deprecated. Use ToolbarsPreset instead. + */ import React from 'react'; import {i18n} from '../../i18n/menubar'; diff --git a/src/bundle/config/wysiwyg.ts b/src/bundle/config/wysiwyg.ts index bc761b6a..db9d1a38 100644 --- a/src/bundle/config/wysiwyg.ts +++ b/src/bundle/config/wysiwyg.ts @@ -1,3 +1,6 @@ +/** + * @deprecated This file is deprecated. Use ToolbarsPreset instead. + */ import {ActionStorage} from 'src/core'; import {headingType, pType} from '../../extensions'; diff --git a/src/bundle/toolbar/utils.ts b/src/bundle/toolbar/utils.ts new file mode 100644 index 00000000..94447bc1 --- /dev/null +++ b/src/bundle/toolbar/utils.ts @@ -0,0 +1,136 @@ +import {ToolbarName} from '../../modules/toolbars/constants'; +import {commonmark, defaultPreset, full, yfm, zero} from '../../modules/toolbars/presets'; +import type { + ToolbarItem, + ToolbarItemMarkup, + ToolbarItemWysiwyg, + ToolbarsPreset, +} from '../../modules/toolbars/types'; +import type {MToolbarData, MToolbarItemData, WToolbarData, WToolbarItemData} from '../../toolbar'; +import {ToolbarDataType, ToolbarIconData} from '../../toolbar'; +import type {MarkdownEditorViewProps} from '../MarkdownEditorView'; +import {MarkdownEditorPreset} from '../types'; + +const defaultPresets: Record = { + zero, + commonmark, + default: defaultPreset, + yfm, + full, +}; + +interface TransformedItem { + type: ToolbarDataType; + id: string; + title?: string | (() => string); + hint?: string | (() => string); + icon?: ToolbarIconData; + hotkey?: string; + withArrow?: boolean; + wysiwyg?: ToolbarItemWysiwyg; + markup?: ToolbarItemMarkup; +} + +const transformItem = ( + type: 'wysiwyg' | 'markup', + item?: ToolbarItem, + id = 'unknown', +): TransformedItem => { + if (!item) { + console.warn( + `Toolbar item "${id}" not found, it might not have been added to the items dictionary.`, + ); + return {} as TransformedItem; + } + + const isListButton = item.view.type === ToolbarDataType.ListButton; + + return { + type: item.view.type ?? ToolbarDataType.SingleButton, + id, + title: item.view.title, + hint: item.view.hint, + icon: item.view.icon, + hotkey: item.view.hotkey, + ...(isListButton && {withArrow: (item.view as any).withArrow}), + ...(type === 'wysiwyg' && item.wysiwyg && {...item.wysiwyg}), + ...(type === 'markup' && item.markup && {...item.markup}), + }; +}; + +export const createConfig = ( + editorType: 'wysiwyg' | 'markup', + toolbarPreset: ToolbarsPreset | MarkdownEditorPreset, + toolbarName: string, +): T => { + const preset = + typeof toolbarPreset === 'string' + ? defaultPresets[toolbarPreset] || defaultPresets.default + : toolbarPreset; + const orders = preset.orders[toolbarName] ?? [[]]; + const {items} = preset; + + const toolbarData = orders.map((group) => + group.map((action) => { + return typeof action === 'string' + ? transformItem(editorType, items[action], action) + : { + ...transformItem(editorType, items[action.id], action.id), + data: action.items.map((id) => transformItem(editorType, items[id], id)), + }; + }), + ); + + return toolbarData as T; +}; + +const flattenPreset = (config: T) => { + // TODO: @makhnatkin add logic for flatten + return (config[0] ?? []) as unknown as T extends WToolbarData + ? WToolbarItemData[] + : MToolbarItemData[]; +}; + +interface GetToolbarsConfigsArgs { + toolbarsPreset?: ToolbarsPreset; + props: Pick< + MarkdownEditorViewProps, + | 'markupToolbarConfig' + | 'wysiwygToolbarConfig' + | 'wysiwygHiddenActionsConfig' + | 'markupHiddenActionsConfig' + >; + preset: MarkdownEditorPreset; +} +export const getToolbarsConfigs = ({toolbarsPreset, props, preset}: GetToolbarsConfigsArgs) => { + const wysiwygToolbarConfig = toolbarsPreset + ? createConfig('wysiwyg', toolbarsPreset, ToolbarName.wysiwygMain) + : props.wysiwygToolbarConfig ?? + createConfig('wysiwyg', preset, ToolbarName.wysiwygMain); + + const markupToolbarConfig = toolbarsPreset + ? createConfig('markup', toolbarsPreset, ToolbarName.markupMain) + : props.markupToolbarConfig ?? + createConfig('markup', preset, ToolbarName.markupMain); + + const wysiwygHiddenActionsConfig = toolbarsPreset + ? flattenPreset( + createConfig('wysiwyg', toolbarsPreset, ToolbarName.wysiwygHidden), + ) + : props.wysiwygHiddenActionsConfig ?? + flattenPreset(createConfig('wysiwyg', preset, ToolbarName.wysiwygHidden)); + + const markupHiddenActionsConfig = toolbarsPreset + ? flattenPreset( + createConfig('markup', toolbarsPreset, ToolbarName.markupHidden), + ) + : props.markupHiddenActionsConfig ?? + flattenPreset(createConfig('markup', preset, ToolbarName.markupHidden)); + + return { + wysiwygToolbarConfig, + markupToolbarConfig, + wysiwygHiddenActionsConfig, + markupHiddenActionsConfig, + }; +}; diff --git a/src/i18n/menubar/en.json b/src/i18n/menubar/en.json index d007825c..7f15a7f5 100644 --- a/src/i18n/menubar/en.json +++ b/src/i18n/menubar/en.json @@ -44,6 +44,7 @@ "mermaid": "Mermaid", "mono": "Monospace", "more_action": "More action", + "move_list": "Move list item", "note": "Note", "olist": "Ordered list", "quote": "Quote", diff --git a/src/i18n/menubar/ru.json b/src/i18n/menubar/ru.json index 10d123d9..378bfac0 100644 --- a/src/i18n/menubar/ru.json +++ b/src/i18n/menubar/ru.json @@ -44,6 +44,7 @@ "mermaid": "Mermaid", "mono": "Моноширинный", "more_action": "Другие действия", + "move_list": "Переместить элемент списка", "note": "Примечание", "olist": "Нумерованный список", "quote": "Цитата", diff --git a/src/modules/toolbars/constants.ts b/src/modules/toolbars/constants.ts new file mode 100644 index 00000000..1b7cd315 --- /dev/null +++ b/src/modules/toolbars/constants.ts @@ -0,0 +1,14 @@ +export enum ListName { + heading = 'heading', + lists = 'lists', + code = 'code', +} + +export enum ToolbarName { + markupHidden = 'markupHidden', + markupMain = 'markupMain', + wysiwygHidden = 'wysiwygHidden', + wysiwygMain = 'wysiwygMain', + wysiwygSelection = 'wysiwygSelection', + wysiwygSlash = 'wysiwygSlash', +} diff --git a/src/modules/toolbars/items.tsx b/src/modules/toolbars/items.tsx new file mode 100644 index 00000000..80331f1d --- /dev/null +++ b/src/modules/toolbars/items.tsx @@ -0,0 +1,813 @@ +import React from 'react'; + +import {icons} from '../../bundle/config/icons'; +import {MToolbarColors} from '../../bundle/toolbar/markup/MToolbarColors'; +import {MToolbarFilePopup} from '../../bundle/toolbar/markup/MToolbarFilePopup'; +import {MToolbarImagePopup} from '../../bundle/toolbar/markup/MToolbarImagePopup'; +import {WToolbarColors} from '../../bundle/toolbar/wysiwyg/WToolbarColors'; +import {WToolbarTextSelect} from '../../bundle/toolbar/wysiwyg/WToolbarTextSelect'; +import {headingType, pType} from '../../extensions'; +import {gptHotKeys} from '../../extensions/additional/GPT/constants'; +import {i18n as i18nHint} from '../../i18n/hints'; +import {i18n} from '../../i18n/menubar'; +import { + insertHRule, + insertLink, + insertMermaidDiagram, + insertYfmHtmlBlock, + insertYfmTable, + insertYfmTabs, + liftListItem as liftListItemCommand, + redo, + redoDepth, + sinkListItem as sinkListItemCommand, + toBulletList, + toH1, + toH2, + toH3, + toH4, + toH5, + toH6, + toOrderedList, + toggleBold, + toggleItalic, + toggleMarked, + toggleMonospace, + toggleStrikethrough, + toggleUnderline, + undo, + undoDepth, + wrapToBlockquote, + wrapToCheckbox, + wrapToCodeBlock, + wrapToInlineCode, + wrapToMathBlock, + wrapToMathInline, + wrapToYfmCut, + wrapToYfmNote, +} from '../../markup/commands'; +import {Action as A, formatter as f} from '../../shortcuts'; +import {ToolbarDataType} from '../../toolbar'; + +import {ToolbarItemMarkup, ToolbarItemView, ToolbarItemWysiwyg} from './types'; + +const noop = () => {}; +const inactive = () => false; +const enable = () => true; +const disable = () => false; + +// ---- Undo ---- +export const undoItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'undo'), + icon: icons.undo, + hotkey: f.toView(A.Undo), +}; +export const undoItemWysiwyg: ToolbarItemWysiwyg = { + hintWhenDisabled: false, + exec: (e) => e.actions.undo.run(), + isActive: (e) => e.actions.undo.isActive(), + isEnable: (e) => e.actions.undo.isEnable(), +}; +export const undoItemMarkup: ToolbarItemMarkup = { + hintWhenDisabled: false, + exec: (e) => undo(e.cm), + isActive: inactive, + isEnable: (e) => undoDepth(e.cm.state) > 0, +}; + +// ---- Redo ---- +export const redoItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'redo'), + icon: icons.redo, + hotkey: f.toView(A.Redo), +}; +export const redoItemWysiwyg: ToolbarItemWysiwyg = { + hintWhenDisabled: false, + exec: (e) => e.actions.redo.run(), + isActive: (e) => e.actions.redo.isActive(), + isEnable: (e) => e.actions.redo.isEnable(), +}; +export const redoItemMarkup: ToolbarItemMarkup = { + hintWhenDisabled: false, + exec: (e) => redo(e.cm), + isActive: inactive, + isEnable: (e) => redoDepth(e.cm.state) > 0, +}; + +// ---- Bold ---- +export const boldItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'bold'), + icon: icons.bold, + hotkey: f.toView(A.Bold), +}; +export const boldItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.bold.run(), + isActive: (e) => e.actions.bold.isActive(), + isEnable: (e) => e.actions.bold.isEnable(), +}; +export const boldItemMarkup: ToolbarItemMarkup = { + exec: (e) => toggleBold(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Italic ---- +export const italicItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'italic'), + icon: icons.italic, + hotkey: f.toView(A.Italic), +}; +export const italicItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.italic.run(), + isActive: (e) => e.actions.italic.isActive(), + isEnable: (e) => e.actions.italic.isEnable(), +}; +export const italicItemMarkup: ToolbarItemMarkup = { + exec: (e) => toggleItalic(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Underline ---- +export const underlineItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'underline'), + icon: icons.underline, + hotkey: f.toView(A.Underline), +}; +export const underlineItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.underline.run(), + isActive: (e) => e.actions.underline.isActive(), + isEnable: (e) => e.actions.underline.isEnable(), +}; +export const underlineItemMarkup: ToolbarItemMarkup = { + exec: (e) => toggleUnderline(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Strikethrough ---- +export const strikethroughItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'strike'), + icon: icons.strikethrough, + hotkey: f.toView(A.Strike), +}; +export const strikethroughItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.strike.run(), + isActive: (e) => e.actions.strike.isActive(), + isEnable: (e) => e.actions.strike.isEnable(), +}; +export const strikethroughItemMarkup: ToolbarItemMarkup = { + exec: (e) => toggleStrikethrough(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Monospace ---- +export const monospaceItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'mono'), + icon: icons.mono, +}; +export const monospaceItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.mono.run(), + isActive: (e) => e.actions.mono.isActive(), + isEnable: (e) => e.actions.mono.isEnable(), +}; +export const monospaceItemMarkup: ToolbarItemMarkup = { + exec: (e) => toggleMonospace(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Marked ---- +export const markedItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'mark'), + icon: icons.mark, +}; +export const markedItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.mark.run(), + isActive: (e) => e.actions.mark.isActive(), + isEnable: (e) => e.actions.mark.isEnable(), +}; +export const markedItemMarkup: ToolbarItemMarkup = { + exec: (e) => toggleMarked(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Checkbox ---- +export const checkboxItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'checkbox'), + icon: icons.checklist, +}; +export const checkboxItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.addCheckbox.run(), + isActive: (e) => e.actions.addCheckbox.isActive(), + isEnable: (e) => e.actions.addCheckbox.isEnable(), +}; +export const checkboxItemMarkup: ToolbarItemMarkup = { + exec: (e) => wrapToCheckbox(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Link ---- +export const linkItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'link'), + icon: icons.link, + hotkey: f.toView(A.Link), +}; +export const linkItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.addLink.run(), + isActive: (e) => e.actions.addLink.isActive(), + isEnable: (e) => e.actions.addLink.isEnable(), +}; +export const linkItemMarkup: ToolbarItemMarkup = { + exec: (e) => insertLink(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Quote ---- +export const quoteItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'quote'), + icon: icons.quote, + hotkey: f.toView(A.Quote), +}; +export const quoteItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.quote.run(), + isActive: (e) => e.actions.quote.isActive(), + isEnable: (e) => e.actions.quote.isEnable(), +}; +export const quoteItemMarkup: ToolbarItemMarkup = { + exec: (e) => wrapToBlockquote(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Cut ---- +export const cutItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'cut'), + icon: icons.cut, + hotkey: f.toView(A.Cut), +}; +export const cutItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.toYfmCut.run(), + isActive: (e) => e.actions.toYfmCut.isActive(), + isEnable: (e) => e.actions.toYfmCut.isEnable(), +}; +export const cutItemMarkup: ToolbarItemMarkup = { + exec: (e) => wrapToYfmCut(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Note ---- +export const noteItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'note'), + icon: icons.note, + hotkey: f.toView(A.Note), +}; +export const noteItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.toYfmNote.run(), + isActive: (e) => e.actions.toYfmNote.isActive(), + isEnable: (e) => e.actions.toYfmNote.isEnable(), +}; +export const noteItemMarkup: ToolbarItemMarkup = { + exec: (e) => wrapToYfmNote(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Table ---- +export const tableItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'table'), + icon: icons.table, +}; +export const tableItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.createYfmTable.run(), + isActive: (e) => e.actions.createYfmTable.isActive(), + isEnable: (e) => e.actions.createYfmTable.isEnable(), +}; +export const tableItemMarkup: ToolbarItemMarkup = { + exec: (e) => insertYfmTable(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Code ---- +export const codeItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'code_inline'), + icon: icons.code, + hotkey: f.toView(A.Code), +}; +export const codeItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.code.run(), + isActive: (e) => e.actions.code.isActive(), + isEnable: (e) => e.actions.code.isEnable(), +}; +export const codeItemMarkup: ToolbarItemMarkup = { + exec: (e) => wrapToInlineCode(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Image ---- +export const imageItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'image'), + icon: icons.image, +}; +export const imagePopupItemView: ToolbarItemView = { + type: ToolbarDataType.ButtonPopup, + title: i18n.bind(null, 'image'), + icon: icons.image, +}; +export const imageItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.addImageWidget.run(), + isActive: (e) => e.actions.addImageWidget.isActive(), + isEnable: (e) => e.actions.addImageWidget.isEnable(), +}; +export const imageItemMarkup: ToolbarItemMarkup = { + exec: noop, + isActive: inactive, + isEnable: enable, + renderPopup: (props) => , +}; + +// ---- File ---- +export const fileItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'file'), + icon: icons.file, +}; +export const filePopupItemView: ToolbarItemView = { + type: ToolbarDataType.ButtonPopup, + title: i18n.bind(null, 'file'), + icon: icons.file, +}; +export const fileItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.addFile.run(), + isActive: (e) => e.actions.addFile.isActive(), + isEnable: (e) => e.actions.addFile.isEnable(), +}; +export const fileItemMarkup: ToolbarItemMarkup = { + exec: noop, + isActive: inactive, + isEnable: enable, + renderPopup: (props) => , +}; + +// ---- Tabs ---- +export const tabsItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'tabs'), + icon: icons.tabs, +}; +export const tabsItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.toYfmTabs.run(), + isActive: (e) => e.actions.toYfmTabs.isActive(), + isEnable: (e) => e.actions.toYfmTabs.isEnable(), +}; +export const tabsItemMarkup: ToolbarItemMarkup = { + exec: (e) => insertYfmTabs(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Math Inline ---- +export const mathInlineItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'math_inline'), + icon: icons.functionInline, + hint: () => `${i18nHint.bind(null, 'math_hint')()} ${i18nHint.bind(null, 'math_hint_katex')()}`, +}; +export const mathInlineItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.addMathInline.run(), + isActive: (e) => e.actions.addMathInline.isActive(), + isEnable: (e) => e.actions.addMathInline.isEnable(), +}; +export const mathInlineItemMarkup: ToolbarItemMarkup = { + exec: (e) => wrapToMathInline(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Math Block ---- +export const mathBlockItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'math_block'), + icon: icons.functionBlock, + hint: () => `${i18nHint.bind(null, 'math_hint')()} ${i18nHint.bind(null, 'math_hint_katex')()}`, +}; +export const mathBlockItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.toMathBlock.run(), + isActive: (e) => e.actions.toMathBlock.isActive(), + isEnable: (e) => e.actions.toMathBlock.isEnable(), +}; +export const mathBlockItemMarkup: ToolbarItemMarkup = { + exec: (e) => wrapToMathBlock(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Yfm Html Block ---- +export const yfmHtmlBlockItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'html'), + icon: icons.html, +}; +export const yfmHtmlBlockItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.createYfmHtmlBlock.run(), + isActive: (e) => e.actions.createYfmHtmlBlock.isActive(), + isEnable: (e) => e.actions.createYfmHtmlBlock.isEnable(), +}; +export const yfmHtmlBlockItemMarkup: ToolbarItemMarkup = { + exec: (e) => insertYfmHtmlBlock(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Mermaid ---- +export const mermaidItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'mermaid'), + icon: icons.mermaid, +}; +export const mermaidItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.createMermaid.run(), + isActive: (e) => e.actions.createMermaid.isActive(), + isEnable: (e) => e.actions.createMermaid.isEnable(), +}; +export const mermaidItemMarkup: ToolbarItemMarkup = { + exec: (e) => insertMermaidDiagram(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Code Block ---- +export const codeBlockItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'codeblock'), + icon: icons.codeBlock, + hotkey: f.toView(A.CodeBlock), +}; +export const codeBlockItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.toCodeBlock.run(), + isActive: (e) => e.actions.toCodeBlock.isActive(), + isEnable: (e) => e.actions.toCodeBlock.isEnable(), +}; +export const codeBlockItemMarkup: ToolbarItemMarkup = { + exec: (e) => wrapToCodeBlock(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Horizontal Rule ---- +export const hruleItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'hrule'), + icon: icons.horizontalRule, +}; +export const hruleItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.hRule.run(), + isActive: (e) => e.actions.hRule.isActive(), + isEnable: (e) => e.actions.hRule.isEnable(), +}; +export const hruleItemMarkup: ToolbarItemMarkup = { + exec: (e) => insertHRule(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Emoji ---- +export const emojiItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'emoji'), + icon: icons.emoji, +}; +export const emojiItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.openEmojiSuggest.run({}), + isActive: (e) => e.actions.openEmojiSuggest.isActive(), + isEnable: (e) => e.actions.openEmojiSuggest.isEnable(), +}; +export const emojiItemMarkup: ToolbarItemMarkup = { + exec: noop, + hintWhenDisabled: i18n.bind(null, 'emoji__hint'), + isActive: inactive, + isEnable: disable, +}; + +// ---- Heading 1 ---- +export const heading1ItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'heading1'), + icon: icons.h1, + hotkey: f.toView(A.Heading1), +}; +export const heading1ItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.toH1.run(), + isActive: (e) => e.actions.toH1.isActive(), + isEnable: (e) => e.actions.toH1.isEnable(), +}; +export const heading1ItemMarkup: ToolbarItemMarkup = { + exec: (e) => toH1(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Heading 2 ---- +export const heading2ItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'heading2'), + icon: icons.h2, + hotkey: f.toView(A.Heading2), +}; +export const heading2ItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.toH2.run(), + isActive: (e) => e.actions.toH2.isActive(), + isEnable: (e) => e.actions.toH2.isEnable(), +}; +export const heading2ItemMarkup: ToolbarItemMarkup = { + exec: (e) => toH2(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Heading 3 ---- +export const heading3ItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'heading3'), + icon: icons.h3, + hotkey: f.toView(A.Heading3), +}; +export const heading3ItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.toH3.run(), + isActive: (e) => e.actions.toH3.isActive(), + isEnable: (e) => e.actions.toH3.isEnable(), +}; +export const heading3ItemMarkup: ToolbarItemMarkup = { + exec: (e) => toH3(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Heading 4 ---- +export const heading4ItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'heading4'), + icon: icons.h4, + hotkey: f.toView(A.Heading4), +}; +export const heading4ItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.toH4.run(), + isActive: (e) => e.actions.toH4.isActive(), + isEnable: (e) => e.actions.toH4.isEnable(), +}; +export const heading4ItemMarkup: ToolbarItemMarkup = { + exec: (e) => toH4(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Heading 5 ---- +export const heading5ItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'heading5'), + icon: icons.h5, + hotkey: f.toView(A.Heading5), +}; +export const heading5ItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.toH5.run(), + isActive: (e) => e.actions.toH5.isActive(), + isEnable: (e) => e.actions.toH5.isEnable(), +}; +export const heading5ItemMarkup: ToolbarItemMarkup = { + exec: (e) => toH5(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Heading 6 ---- +export const heading6ItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'heading6'), + icon: icons.h6, + hotkey: f.toView(A.Heading6), +}; +export const heading6ItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.toH6.run(), + isActive: (e) => e.actions.toH6.isActive(), + isEnable: (e) => e.actions.toH6.isEnable(), +}; +export const heading6ItemMarkup: ToolbarItemMarkup = { + exec: (e) => toH6(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Bullet List ---- +export const bulletListItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'ulist'), + icon: icons.bulletList, + hotkey: f.toView(A.BulletList), +}; +export const bulletListItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.toBulletList.run(), + isActive: (e) => e.actions.toBulletList.isActive(), + isEnable: (e) => e.actions.toBulletList.isEnable(), +}; +export const bulletListItemMarkup: ToolbarItemMarkup = { + exec: (e) => toBulletList(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Ordered List ---- +export const orderedListItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'olist'), + icon: icons.orderedList, + hotkey: f.toView(A.OrderedList), +}; +export const orderedListItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.toOrderedList.run(), + isActive: (e) => e.actions.toOrderedList.isActive(), + isEnable: (e) => e.actions.toOrderedList.isEnable(), +}; +export const orderedListItemMarkup: ToolbarItemMarkup = { + exec: (e) => toOrderedList(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Sink List ---- +export const sinkListItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'list__action_sink'), + icon: icons.sink, + hotkey: f.toView(A.SinkListItem), +}; +export const sinkListItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.sinkListItem.run(), + hintWhenDisabled: () => i18n('list_action_disabled'), + isActive: (e) => e.actions.sinkListItem.isActive(), + isEnable: (e) => e.actions.sinkListItem.isEnable(), +}; +export const sinkListItemMarkup: ToolbarItemMarkup = { + exec: (e) => sinkListItemCommand(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Lift List ---- +export const liftListItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'list__action_lift'), + icon: icons.lift, + hotkey: f.toView(A.LiftListItem), +}; +export const liftListItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.liftListItem.run(), + hintWhenDisabled: () => i18n('list_action_disabled'), + isActive: (e) => e.actions.liftListItem.isActive(), + isEnable: (e) => e.actions.liftListItem.isEnable(), +}; +export const liftListItemMarkup: ToolbarItemMarkup = { + exec: (e) => liftListItemCommand(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Toggle Heading Folding ---- +export const toggleHeadingFoldingItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + icon: icons.foldingHeading, + title: () => i18n('folding-heading'), + hint: () => i18n('folding-heading__hint'), +}; +export const toggleHeadingFoldingItemWysiwyg: ToolbarItemWysiwyg = { + isActive: (editor) => editor.actions.toggleHeadingFolding?.isActive() ?? false, + isEnable: (editor) => editor.actions.toggleHeadingFolding?.isEnable() ?? false, + exec: (editor) => editor.actions.toggleHeadingFolding.run(), + condition: 'enabled', +}; + +// ---- Text Context ---- +export const textContextItemView: ToolbarItemView = { + type: ToolbarDataType.ReactComponent, +}; +export const textContextItemWisywig: ToolbarItemWysiwyg = { + component: WToolbarTextSelect, + width: 0, + condition: ({selection: {$from, $to}, schema}) => { + if (!$from.sameParent($to)) return false; + const {parent} = $from; + return parent.type === pType(schema) || parent.type === headingType(schema); + }, +}; + +// ---- Paragraph ---- +export const paragraphItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'text'), + icon: icons.text, + hotkey: f.toView(A.Text), + doNotActivateList: true, +}; +export const paragraphItemWisywig: ToolbarItemWysiwyg = { + exec: (e) => e.actions.toParagraph.run(), + isActive: (e) => e.actions.toParagraph.isActive(), + isEnable: (e) => e.actions.toParagraph.isEnable(), +}; +export const paragraphItemMarkup: ToolbarItemMarkup = { + exec: noop, + isActive: inactive, + isEnable: enable, +}; + +// --- Colorify ---- +export const colorifyItemView: ToolbarItemView = { + type: ToolbarDataType.ReactComponent, +}; +export const colorifyItemWysiwyg: ToolbarItemWysiwyg = { + component: WToolbarColors, + width: 42, +}; +export const colorifyItemMarkup: ToolbarItemMarkup = { + component: MToolbarColors, + width: 42, +}; + +// ---- GPT ---- +export const gptItemView: ToolbarItemView = { + type: ToolbarDataType.SingleButton, + title: i18n.bind(null, 'gpt'), + hotkey: gptHotKeys.openGptKeyTooltip, + icon: icons.gpt, +}; +export const gptItemWysiwyg: ToolbarItemWysiwyg = { + exec: (e) => e.actions.addGptWidget.run({}), + isActive: (e) => e.actions.addGptWidget.isActive(), + isEnable: (e) => e.actions.addGptWidget.isEnable(), +}; +export const gptItemMarkup: ToolbarItemMarkup = { + exec: (e) => insertMermaidDiagram(e.cm), + isActive: inactive, + isEnable: enable, +}; + +// ---- Heading list ---- +export const headingListItemView: ToolbarItemView = { + type: ToolbarDataType.ListButton, + icon: icons.headline, + title: i18n.bind(null, 'heading'), + withArrow: true, +}; + +// ---- Lists list ---- +export const listsListItemView: ToolbarItemView = { + type: ToolbarDataType.ListButton, + icon: icons.bulletList, + withArrow: true, + title: i18n.bind(null, 'list'), +}; + +// ---- Move list ---- +export const moveListItemView: ToolbarItemView = { + type: ToolbarDataType.ListButton, + icon: icons.lift, + withArrow: true, + title: i18n.bind(null, 'move_list'), +}; + +// ---- Code list ---- +export const codeBlocksListItemView: ToolbarItemView = { + type: ToolbarDataType.ListButton, + icon: icons.code, + title: i18n.bind(null, 'code'), + withArrow: true, +}; + +// ---- Math list ---- +export const mathListItemView: ToolbarItemView = { + type: ToolbarDataType.ListButton, + icon: icons.functionInline, + withArrow: true, + title: i18n.bind(null, 'math'), +}; diff --git a/src/modules/toolbars/presets.ts b/src/modules/toolbars/presets.ts new file mode 100644 index 00000000..531f8324 --- /dev/null +++ b/src/modules/toolbars/presets.ts @@ -0,0 +1,614 @@ +import {ActionName as Action} from '../../bundle/config/action-names'; + +import {ListName as List, ToolbarName as Toolbar} from './constants'; +import { + boldItemMarkup, + boldItemView, + boldItemWysiwyg, + bulletListItemMarkup, + bulletListItemView, + bulletListItemWysiwyg, + checkboxItemMarkup, + checkboxItemView, + checkboxItemWysiwyg, + codeBlockItemMarkup, + codeBlockItemView, + codeBlockItemWysiwyg, + codeBlocksListItemView, + codeItemMarkup, + codeItemView, + codeItemWysiwyg, + colorifyItemMarkup, + colorifyItemView, + colorifyItemWysiwyg, + cutItemMarkup, + cutItemView, + cutItemWysiwyg, + emojiItemMarkup, + emojiItemView, + emojiItemWysiwyg, + fileItemMarkup, + fileItemView, + fileItemWysiwyg, + filePopupItemView, + heading1ItemMarkup, + heading1ItemView, + heading1ItemWysiwyg, + heading2ItemMarkup, + heading2ItemView, + heading2ItemWysiwyg, + heading3ItemMarkup, + heading3ItemView, + heading3ItemWysiwyg, + heading4ItemMarkup, + heading4ItemView, + heading4ItemWysiwyg, + heading5ItemMarkup, + heading5ItemView, + heading5ItemWysiwyg, + heading6ItemMarkup, + heading6ItemView, + heading6ItemWysiwyg, + headingListItemView, + hruleItemMarkup, + hruleItemView, + hruleItemWysiwyg, + imageItemMarkup, + imageItemView, + imageItemWysiwyg, + imagePopupItemView, + italicItemMarkup, + italicItemView, + italicItemWysiwyg, + liftListItemMarkup, + liftListItemView, + liftListItemWysiwyg, + linkItemMarkup, + linkItemView, + linkItemWysiwyg, + listsListItemView, + markedItemMarkup, + markedItemView, + markedItemWysiwyg, + monospaceItemMarkup, + monospaceItemView, + monospaceItemWysiwyg, + noteItemMarkup, + noteItemView, + noteItemWysiwyg, + orderedListItemMarkup, + orderedListItemView, + orderedListItemWysiwyg, + paragraphItemMarkup, + paragraphItemView, + paragraphItemWisywig, + quoteItemMarkup, + quoteItemView, + quoteItemWysiwyg, + redoItemMarkup, + redoItemView, + redoItemWysiwyg, + sinkListItemMarkup, + sinkListItemView, + sinkListItemWysiwyg, + strikethroughItemMarkup, + strikethroughItemView, + tableItemMarkup, + tableItemView, + tableItemWysiwyg, + underlineItemMarkup, + underlineItemView, + underlineItemWysiwyg, + undoItemMarkup, + undoItemView, + undoItemWysiwyg, +} from './items'; +import {ToolbarsPreset} from './types'; + +// presets +export const zero: ToolbarsPreset = { + items: { + [Action.undo]: { + view: undoItemView, + wysiwyg: undoItemWysiwyg, + markup: undoItemMarkup, + }, + [Action.redo]: { + view: redoItemView, + wysiwyg: redoItemWysiwyg, + markup: redoItemMarkup, + }, + }, + orders: { + [Toolbar.wysiwygMain]: [[Action.undo, Action.redo]], + [Toolbar.markupMain]: [[Action.undo, Action.redo]], + }, +}; + +export const commonmark: ToolbarsPreset = { + items: { + ...zero.items, + [Action.bold]: { + view: boldItemView, + wysiwyg: boldItemWysiwyg, + markup: boldItemMarkup, + }, + [Action.italic]: { + view: italicItemView, + wysiwyg: italicItemWysiwyg, + markup: italicItemMarkup, + }, + [List.heading]: { + view: headingListItemView, + }, + [Action.paragraph]: { + view: paragraphItemView, + wysiwyg: paragraphItemWisywig, + markup: paragraphItemMarkup, + }, + [Action.heading1]: { + view: heading1ItemView, + wysiwyg: heading1ItemWysiwyg, + markup: heading1ItemMarkup, + }, + [Action.heading2]: { + view: heading2ItemView, + wysiwyg: heading2ItemWysiwyg, + markup: heading2ItemMarkup, + }, + [Action.heading3]: { + view: heading3ItemView, + wysiwyg: heading3ItemWysiwyg, + markup: heading3ItemMarkup, + }, + [Action.heading4]: { + view: heading4ItemView, + wysiwyg: heading4ItemWysiwyg, + markup: heading4ItemMarkup, + }, + [Action.heading5]: { + view: heading5ItemView, + wysiwyg: heading5ItemWysiwyg, + markup: heading5ItemMarkup, + }, + [Action.heading6]: { + view: heading6ItemView, + wysiwyg: heading6ItemWysiwyg, + markup: heading6ItemMarkup, + }, + [List.lists]: { + view: listsListItemView, + }, + [Action.bulletList]: { + view: bulletListItemView, + wysiwyg: bulletListItemWysiwyg, + markup: bulletListItemMarkup, + }, + [Action.orderedList]: { + view: orderedListItemView, + wysiwyg: orderedListItemWysiwyg, + markup: orderedListItemMarkup, + }, + [Action.sinkListItem]: { + view: sinkListItemView, + wysiwyg: sinkListItemWysiwyg, + markup: sinkListItemMarkup, + }, + [Action.liftListItem]: { + view: liftListItemView, + wysiwyg: liftListItemWysiwyg, + markup: liftListItemMarkup, + }, + [Action.link]: { + view: linkItemView, + wysiwyg: linkItemWysiwyg, + markup: linkItemMarkup, + }, + [Action.quote]: { + view: quoteItemView, + wysiwyg: quoteItemWysiwyg, + markup: quoteItemMarkup, + }, + [List.code]: { + view: codeBlocksListItemView, + }, + [Action.codeInline]: { + view: codeItemView, + wysiwyg: codeItemWysiwyg, + markup: codeItemMarkup, + }, + [Action.codeBlock]: { + view: codeBlockItemView, + wysiwyg: codeBlockItemWysiwyg, + markup: codeBlockItemMarkup, + }, + [Action.horizontalRule]: { + view: hruleItemView, + wysiwyg: hruleItemWysiwyg, + markup: hruleItemMarkup, + }, + }, + orders: { + [Toolbar.wysiwygMain]: [ + [Action.undo, Action.redo], + [Action.bold, Action.italic], + [ + { + id: List.heading, + items: [ + Action.paragraph, + Action.heading1, + Action.heading2, + Action.heading3, + Action.heading4, + Action.heading5, + Action.heading6, + ], + }, + { + id: List.lists, + items: [ + Action.bulletList, + Action.orderedList, + Action.sinkListItem, + Action.liftListItem, + ], + }, + Action.link, + Action.quote, + { + id: List.code, + items: [Action.codeInline, Action.codeBlock], + }, + ], + ], + [Toolbar.markupMain]: [ + [Action.undo, Action.redo], + [Action.bold, Action.italic], + [ + { + id: List.heading, + items: [ + Action.paragraph, + Action.heading1, + Action.heading2, + Action.heading3, + Action.heading4, + Action.heading5, + Action.heading6, + ], + }, + { + id: List.lists, + items: [ + Action.bulletList, + Action.orderedList, + Action.sinkListItem, + Action.liftListItem, + ], + }, + Action.link, + Action.quote, + { + id: List.code, + items: [Action.codeInline, Action.codeBlock], + }, + ], + ], + [Toolbar.wysiwygHidden]: [[Action.horizontalRule]], + [Toolbar.markupHidden]: [[Action.horizontalRule]], + }, +}; + +export const defaultPreset: ToolbarsPreset = { + items: { + ...commonmark.items, + [Action.strike]: { + view: strikethroughItemView, + wysiwyg: sinkListItemWysiwyg, + markup: strikethroughItemMarkup, + }, + }, + orders: { + [Toolbar.wysiwygMain]: [ + [Action.undo, Action.redo], + [Action.bold, Action.italic, Action.strike], + [ + { + id: List.heading, + items: [ + Action.paragraph, + Action.heading1, + Action.heading2, + Action.heading3, + Action.heading4, + Action.heading5, + Action.heading6, + ], + }, + { + id: List.lists, + items: [ + Action.bulletList, + Action.orderedList, + Action.sinkListItem, + Action.liftListItem, + ], + }, + Action.link, + Action.quote, + { + id: List.code, + items: [Action.codeInline, Action.codeBlock], + }, + ], + ], + [Toolbar.markupMain]: [ + [Action.undo, Action.redo], + [Action.bold, Action.italic, Action.strike], + [ + { + id: List.heading, + items: [ + Action.paragraph, + Action.heading1, + Action.heading2, + Action.heading3, + Action.heading4, + Action.heading5, + Action.heading6, + ], + }, + { + id: List.lists, + items: [ + Action.bulletList, + Action.orderedList, + Action.sinkListItem, + Action.liftListItem, + ], + }, + Action.link, + Action.quote, + { + id: List.code, + items: [Action.codeInline, Action.codeBlock], + }, + ], + ], + [Toolbar.wysiwygHidden]: [[Action.horizontalRule]], + [Toolbar.markupHidden]: [[Action.horizontalRule]], + }, +}; + +export const yfm: ToolbarsPreset = { + items: { + ...defaultPreset.items, + [Action.underline]: { + view: underlineItemView, + wysiwyg: underlineItemWysiwyg, + markup: underlineItemMarkup, + }, + [Action.mono]: { + view: monospaceItemView, + wysiwyg: monospaceItemWysiwyg, + markup: monospaceItemMarkup, + }, + [Action.note]: { + view: noteItemView, + wysiwyg: noteItemWysiwyg, + markup: noteItemMarkup, + }, + [Action.cut]: { + view: cutItemView, + wysiwyg: cutItemWysiwyg, + markup: cutItemMarkup, + }, + [Action.image]: { + view: imageItemView, + wysiwyg: imageItemWysiwyg, + }, + [Action.imagePopup]: { + view: imagePopupItemView, + markup: imageItemMarkup, + }, + [Action.file]: { + view: fileItemView, + wysiwyg: fileItemWysiwyg, + }, + [Action.filePopup]: { + view: filePopupItemView, + markup: fileItemMarkup, + }, + [Action.table]: { + view: tableItemView, + wysiwyg: tableItemWysiwyg, + markup: tableItemMarkup, + }, + [Action.checkbox]: { + view: checkboxItemView, + wysiwyg: checkboxItemWysiwyg, + markup: checkboxItemMarkup, + }, + [Action.tabs]: { + view: tableItemView, + wysiwyg: tableItemWysiwyg, + markup: tableItemMarkup, + }, + }, + orders: { + [Toolbar.wysiwygMain]: [ + [Action.undo, Action.redo], + [Action.bold, Action.italic, Action.underline, Action.strike, Action.mono], + [ + { + id: List.heading, + items: [ + Action.paragraph, + Action.heading1, + Action.heading2, + Action.heading3, + Action.heading4, + Action.heading5, + Action.heading6, + ], + }, + { + id: List.lists, + items: [ + Action.bulletList, + Action.orderedList, + Action.sinkListItem, + Action.liftListItem, + ], + }, + Action.link, + Action.note, + Action.cut, + Action.quote, + { + id: List.code, + items: [Action.codeInline, Action.codeBlock], + }, + ], + [Action.image, Action.file, Action.table, Action.checkbox], + ], + [Toolbar.markupMain]: [ + [Action.undo, Action.redo], + [Action.bold, Action.italic, Action.underline, Action.strike, Action.mono], + [ + { + id: List.heading, + items: [ + Action.paragraph, + Action.heading1, + Action.heading2, + Action.heading3, + Action.heading4, + Action.heading5, + Action.heading6, + ], + }, + { + id: List.lists, + items: [ + Action.bulletList, + Action.orderedList, + Action.sinkListItem, + Action.liftListItem, + ], + }, + Action.link, + Action.note, + Action.cut, + Action.quote, + { + id: List.code, + items: [Action.codeInline, Action.codeBlock], + }, + ], + [Action.imagePopup, Action.filePopup, Action.table, Action.checkbox], + ], + [Toolbar.wysiwygHidden]: [[Action.horizontalRule, Action.tabs]], + [Toolbar.markupHidden]: [[Action.horizontalRule, Action.tabs]], + }, +}; + +export const full: ToolbarsPreset = { + items: { + ...yfm.items, + [Action.mark]: { + view: markedItemView, + wysiwyg: markedItemWysiwyg, + markup: markedItemMarkup, + }, + [Action.colorify]: { + view: colorifyItemView, + wysiwyg: colorifyItemWysiwyg, + markup: colorifyItemMarkup, + }, + [Action.emoji]: { + view: emojiItemView, + wysiwyg: emojiItemWysiwyg, + markup: emojiItemMarkup, + }, + }, + orders: { + [Toolbar.wysiwygMain]: [ + [Action.undo, Action.redo], + [Action.bold, Action.italic, Action.underline, Action.strike, Action.mono, Action.mark], + [ + { + id: List.heading, + items: [ + Action.paragraph, + Action.heading1, + Action.heading2, + Action.heading3, + Action.heading4, + Action.heading5, + Action.heading6, + ], + }, + { + id: List.lists, + items: [ + Action.bulletList, + Action.orderedList, + Action.sinkListItem, + Action.liftListItem, + ], + }, + Action.colorify, + Action.link, + Action.note, + Action.cut, + Action.quote, + { + id: List.code, + items: [Action.codeInline, Action.codeBlock], + }, + ], + [Action.image, Action.file, Action.table, Action.checkbox], + ], + [Toolbar.markupMain]: [ + [Action.undo, Action.redo], + [Action.bold, Action.italic, Action.underline, Action.strike, Action.mono, Action.mark], + [ + { + id: List.heading, + items: [ + Action.paragraph, + Action.heading1, + Action.heading2, + Action.heading3, + Action.heading4, + Action.heading5, + Action.heading6, + ], + }, + { + id: List.lists, + items: [ + Action.bulletList, + Action.orderedList, + Action.sinkListItem, + Action.liftListItem, + ], + }, + Action.colorify, + Action.link, + Action.note, + Action.cut, + Action.quote, + { + id: List.code, + items: [Action.codeInline, Action.codeBlock], + }, + ], + [Action.imagePopup, Action.filePopup, Action.table, Action.checkbox], + ], + [Toolbar.wysiwygHidden]: [[Action.horizontalRule, Action.emoji, Action.tabs]], + [Toolbar.markupHidden]: [[Action.horizontalRule, Action.emoji, Action.tabs]], + }, +}; diff --git a/src/modules/toolbars/types.ts b/src/modules/toolbars/types.ts new file mode 100644 index 00000000..ec3e2de6 --- /dev/null +++ b/src/modules/toolbars/types.ts @@ -0,0 +1,86 @@ +import type {RefObject} from 'react'; + +import type {HotkeyProps} from '@gravity-ui/uikit'; +import type {EditorState} from 'prosemirror-state'; + +import type {ActionStorage} from '../../core'; +import type {CodeEditor} from '../../markup'; +import type {ToolbarBaseProps, ToolbarDataType, ToolbarIconData} from '../../toolbar'; + +// Items +export type ToolbarItemId = string & {}; +export type ToolbarListId = string & {}; + +export interface ToolbarList { + id: ToolbarListId; + items: ToolbarItemId[]; +} + +/** + * The default value for the `type` property is `ToolbarDataType.SingleButton`. + */ +export type ToolbarItemView = { + className?: string; + hint?: string | (() => string); + hotkey?: HotkeyProps['value']; + type?: ToolbarDataType; + doNotActivateList?: boolean; +} & (T extends ToolbarDataType.SingleButton + ? { + icon: ToolbarIconData; + title: string | (() => string); + } + : T extends ToolbarDataType.ListButton + ? { + withArrow?: boolean; + icon: ToolbarIconData; + title: string | (() => string); + } + : {}); + +export interface EditorActions { + exec(editor: E): void; + isActive(editor: E): boolean; + isEnable(editor: E): boolean; +} + +type ToolbarItemEditor = Partial> & { + hintWhenDisabled?: boolean | string | (() => string); + condition?: ((state: EditorState) => void) | 'enabled'; +} & (T extends ToolbarDataType.ButtonPopup + ? { + renderPopup: ( + props: ToolbarBaseProps & { + hide: () => void; + anchorRef: RefObject; + }, + ) => React.ReactNode; + } + : T extends ToolbarDataType.ReactComponent + ? { + width: number; + component: React.ComponentType>; + } + : {}); + +export type ToolbarItemWysiwyg = + ToolbarItemEditor; +export type ToolbarItemMarkup = + ToolbarItemEditor; + +export type ToolbarItem = { + view: ToolbarItemView; + wysiwyg?: ToolbarItemWysiwyg; + markup?: ToolbarItemMarkup; +}; +export type ToolbarsItems = Record>; + +// Orders +export type ToolbarId = string; +export type ToolbarOrders = (ToolbarList | ToolbarItemId)[][]; +export type ToolbarsOrders = Record; + +export interface ToolbarsPreset { + items: ToolbarsItems; + orders: ToolbarsOrders; +} diff --git a/src/toolbar/types.ts b/src/toolbar/types.ts index 50da041c..4061c797 100644 --- a/src/toolbar/types.ts +++ b/src/toolbar/types.ts @@ -43,7 +43,9 @@ export enum ToolbarDataType { SingleButton = 's-button', ListButton = 'list-b', ButtonPopup = 'b-popup', + /** @deprecated Use ReactComponent type instead */ ReactNode = 'r-node', + /** @deprecated Use ReactComponent type instead */ ReactNodeFn = 'r-node-fn', ReactComponent = 'r-component', } @@ -90,6 +92,9 @@ export type ToolbarListButtonItemData = ToolbarItemData & { doNotActivateList?: boolean; }; +/** + * @deprecated Use ReactComponent type instead + * */ export type ToolbarReactNodeData = { id: string; type: ToolbarDataType.ReactNode; @@ -97,6 +102,9 @@ export type ToolbarReactNodeData = { content: React.ReactNode; }; +/** + * @deprecated Use ReactComponent type instead + * */ export type ToolbarReactNodeFnData = { id: string; type: ToolbarDataType.ReactNodeFn;