From b536dc054790900eafd3997a8d7e88fdd396163a Mon Sep 17 00:00:00 2001 From: makhnatkin Date: Wed, 18 Dec 2024 09:56:42 +0100 Subject: [PATCH 1/4] feat(toolbars): added ToolbarsPreset --- demo/components/Playground.tsx | 104 ++- demo/stories/presets/Preset.tsx | 46 ++ demo/stories/presets/Presets.stories.tsx | 62 ++ src/bundle/MarkdownEditorView.tsx | 132 +++- src/bundle/config/action-names.ts | 77 ++- src/bundle/config/index.ts | 3 + src/bundle/config/markup.tsx | 210 +----- src/bundle/config/wysiwyg.ts | 159 +---- src/modules/toolbars/constants.ts | 34 + src/modules/toolbars/items.tsx | 813 +++++++++++++++++++++++ src/modules/toolbars/presets.ts | 632 ++++++++++++++++++ src/modules/toolbars/types.ts | 82 +++ src/toolbar/types.ts | 8 + 13 files changed, 1901 insertions(+), 461 deletions(-) create mode 100644 src/modules/toolbars/constants.ts create mode 100644 src/modules/toolbars/items.tsx create mode 100644 src/modules/toolbars/presets.ts create mode 100644 src/modules/toolbars/types.ts 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..c42a53de 100644 --- a/demo/stories/presets/Preset.tsx +++ b/demo/stories/presets/Preset.tsx @@ -1,5 +1,6 @@ import React, {CSSProperties, useCallback, useEffect} from 'react'; +import {defaultOptions} from '@diplodoc/transform/lib/sanitize'; import {toaster} from '@gravity-ui/uikit/toaster-singleton-react-18'; import { @@ -11,6 +12,12 @@ import { logger, useMarkdownEditor, } from '../../../src'; +import {FoldingHeading} from '../../../src/extensions/additional/FoldingHeading'; +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 {ToolbarsPreset} from '../../../src/modules/toolbars/types'; import type {FileUploadHandler} from '../../../src/utils/upload'; import {VERSION} from '../../../src/version'; // --- @@ -18,6 +25,7 @@ import {WysiwygSelection} from '../../components/PMSelection'; import {WysiwygDevTools} from '../../components/ProseMirrorDevTools'; import {SplitModePreview} from '../../components/SplitModePreview'; import {plugins} from '../../defaults/md-plugins'; +import useYfmHtmlBlockStyles from '../../hooks/useYfmHtmlBlockStyles'; import {block} from '../../utils/cn'; import {randomDelay} from '../../utils/delay'; import {parseInsertedUrlAsImage} from '../../utils/imageUrl'; @@ -41,6 +49,7 @@ export type PresetDemoProps = { splitModeOrientation?: 'horizontal' | 'vertical' | false; stickyToolbar?: boolean; height?: CSSProperties['height']; + toolbarsPreset?: ToolbarsPreset; }; logger.setLogger({ @@ -60,6 +69,7 @@ export const Preset = React.memo((props) => { splitModeOrientation, stickyToolbar, height, + toolbarsPreset, } = props; const [editorMode, setEditorMode] = React.useState('wysiwyg'); const [mdRaw, setMdRaw] = React.useState(''); @@ -96,6 +106,41 @@ export const Preset = React.memo((props) => { parseInsertedUrlAsImage, }, }, + 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) => {
= { @@ -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..3a9bf286 100644 --- a/src/bundle/MarkdownEditorView.tsx +++ b/src/bundle/MarkdownEditorView.tsx @@ -7,7 +7,11 @@ import {useEnsuredForwardedRef, useKey, useUpdate} from 'react-use'; import {ClassNameProps, cn} from '../classname'; import {i18n} from '../i18n/bundle'; import {logger} from '../logger'; +import {ToolbarName} from '../modules/toolbars/constants'; +import {commonmark, defaultPreset, full, yfm, zero} from '../modules/toolbars/presets'; +import {EditorPreset, ToolbarItem, ToolbarsPresetOrEditorPreset} from '../modules/toolbars/types'; import {ToasterContext, useBooleanState, useSticky} from '../react-utils'; +import {ToolbarDataType} from '../toolbar'; import {isMac} from '../utils'; import type {Editor, EditorInt} from './Editor'; @@ -15,16 +19,7 @@ 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'; @@ -39,9 +34,22 @@ const b = cnEditorComponent; export type MarkdownEditorViewProps = ClassNameProps & { editor?: Editor; autofocus?: boolean; + toolbarsPreset?: ToolbarsPresetOrEditorPreset; + /** + * @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; @@ -51,6 +59,68 @@ export type MarkdownEditorViewProps = ClassNameProps & { hidePreviewAfterSubmit?: boolean; }; +const defaultPresets = { + zero, + commonmark, + default: defaultPreset, + yfm, + full, +}; + +const transformItem = ( + type: 'wysiwyg' | 'markup', + item?: ToolbarItem, + id = 'unknown', +) => { + if (!item) return null; + const isListButton = item.view.type === ToolbarDataType.ListButton; + + return { + type: item.view.type || 's-button', + 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 = < + T extends WToolbarData | MToolbarData | WToolbarItemData[][] | MToolbarItemData[][], +>( + editorType: 'wysiwyg' | 'markup', + toolbarPreset: ToolbarsPresetOrEditorPreset, + toolbarName: string, +): T => { + const preset = + typeof toolbarPreset === 'string' ? getDefaultPresetByName(toolbarPreset) : toolbarPreset; + const orders = preset.orders[toolbarName] ?? [[]]; + const {items} = preset; + + const toolbarData = orders.map((group) => + group.map((item) => { + return typeof item === 'string' + ? transformItem(editorType, items[item], item) + : { + ...transformItem(editorType, items[item.id], item.id), + data: item.items.map((id) => transformItem(editorType, items[id], id)), + }; + }), + ); + + return toolbarData as T; +}; + +const getDefaultPresetByName = (initialPreset: EditorPreset) => { + const presetName = ['zero', 'commonmark', 'default', 'yfm', 'full'].includes(initialPreset) + ? initialPreset + : 'default'; + return defaultPresets[presetName]; +}; + export const MarkdownEditorView = React.forwardRef( (props, ref) => { const divRef = useEnsuredForwardedRef(ref as MutableRefObject); @@ -73,16 +143,50 @@ export const MarkdownEditorView = React.forwardRef('wysiwyg', toolbarsPreset, ToolbarName.wysiwygMain) + : props.wysiwygToolbarConfig ?? + createConfig('wysiwyg', editor.preset, ToolbarName.wysiwygMain); + + const markupToolbarConfig = toolbarsPreset + ? createConfig('markup', toolbarsPreset, ToolbarName.markupMain) + : props.markupToolbarConfig ?? + createConfig('markup', editor.preset, ToolbarName.markupMain); + + // TODO: @makhnatkin add getToolbarItemDataFromToolbarData + const wysiwygHiddenActionsConfig = toolbarsPreset + ? createConfig( + 'wysiwyg', + toolbarsPreset, + ToolbarName.wysiwygHidden, + )[0] + : props.wysiwygHiddenActionsConfig ?? + createConfig( + 'wysiwyg', + editor.preset, + ToolbarName.wysiwygHidden, + )[0]; + + const markupHiddenActionsConfig = toolbarsPreset + ? createConfig( + 'markup', + toolbarsPreset, + ToolbarName.markupHidden, + )[0] + : props.markupHiddenActionsConfig ?? + createConfig( + 'markup', + editor.preset, + ToolbarName.markupHidden, + )[0]; + 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..c8babf3d 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'; @@ -54,7 +57,6 @@ import { import {MToolbarColors} from '../toolbar/markup/MToolbarColors'; import {MToolbarFilePopup} from '../toolbar/markup/MToolbarFilePopup'; import {MToolbarImagePopup} from '../toolbar/markup/MToolbarImagePopup'; -import type {MarkdownEditorPreset} from '../types'; import {ActionName} from './action-names'; import {icons} from './icons'; @@ -542,209 +544,3 @@ export const mToolbarConfig: MToolbarData = [ ], [mImagePopupData, mFilePopupData, mTableButton, mCheckboxButton], ]; - -export const mToolbarConfigByPreset: Record = { - zero: [mHistoryGroupConfig], - commonmark: [ - [mUndoItemData, mRedoItemData], - [mBoldItemData, mItalicItemData], - [ - { - id: 'heading', - type: ToolbarDataType.ListButton, - icon: icons.headline, - withArrow: true, - title: i18n.bind(null, 'heading'), - data: [ - mHeading1ItemData, - mHeading2ItemData, - mHeading3ItemData, - mHeading4ItemData, - mHeading5ItemData, - mHeading6ItemData, - ], - }, - { - id: 'list', - type: ToolbarDataType.ListButton, - icon: icons.bulletList, - withArrow: true, - title: i18n.bind(null, 'list'), - data: [mBulletListItemData, mOrderedListItemData], - }, - mLinkButton, - mQuoteButton, - { - id: 'code', - type: ToolbarDataType.ListButton, - icon: icons.code, - withArrow: true, - title: i18n.bind(null, 'code'), - data: [mCodeItemData, mCodeblockItemData], - }, - ], - ], - default: [ - [mUndoItemData, mRedoItemData], - [mBoldItemData, mItalicItemData, mStrikethroughItemData], - [ - { - id: 'heading', - type: ToolbarDataType.ListButton, - icon: icons.headline, - withArrow: true, - title: i18n.bind(null, 'heading'), - data: [ - mHeading1ItemData, - mHeading2ItemData, - mHeading3ItemData, - mHeading4ItemData, - mHeading5ItemData, - mHeading6ItemData, - ], - }, - { - id: 'list', - type: ToolbarDataType.ListButton, - icon: icons.bulletList, - withArrow: true, - title: i18n.bind(null, 'list'), - data: [mBulletListItemData, mOrderedListItemData], - }, - mLinkButton, - mQuoteButton, - { - id: 'code', - type: ToolbarDataType.ListButton, - icon: icons.code, - withArrow: true, - title: i18n.bind(null, 'code'), - data: [mCodeItemData, mCodeblockItemData], - }, - ], - ], - yfm: [ - [mUndoItemData, mRedoItemData], - [ - mBoldItemData, - mItalicItemData, - mUnderlineItemData, - mStrikethroughItemData, - mMonospaceItemData, - ], - [ - { - id: 'heading', - type: ToolbarDataType.ListButton, - icon: icons.headline, - withArrow: true, - title: i18n.bind(null, 'heading'), - data: [ - mHeading1ItemData, - mHeading2ItemData, - mHeading3ItemData, - mHeading4ItemData, - mHeading5ItemData, - mHeading6ItemData, - ], - }, - { - id: 'list', - type: ToolbarDataType.ListButton, - icon: icons.bulletList, - withArrow: true, - title: i18n.bind(null, 'list'), - data: [mBulletListItemData, mOrderedListItemData], - }, - mLinkButton, - mNoteButton, - mCutButton, - mQuoteButton, - { - id: 'code', - type: ToolbarDataType.ListButton, - icon: icons.code, - withArrow: true, - title: i18n.bind(null, 'code'), - data: [mCodeItemData, mCodeblockItemData], - }, - ], - [mImagePopupData, mFilePopupData, mTableButton, mCheckboxButton], - ], - full: mToolbarConfig.slice(), -}; - -export const mHiddenDataByPreset: Record = { - zero: [], - commonmark: [ - mHeading1ItemData, - mHeading2ItemData, - mHeading3ItemData, - mHeading4ItemData, - mHeading5ItemData, - mHeading6ItemData, - mBulletListItemData, - mOrderedListItemData, - mLinkButton, - mQuoteButton, - mCodeblockItemData, - mHruleItemData, - ], - default: [ - mHeading1ItemData, - mHeading2ItemData, - mHeading3ItemData, - mHeading4ItemData, - mHeading5ItemData, - mHeading6ItemData, - mBulletListItemData, - mOrderedListItemData, - mLinkButton, - mQuoteButton, - mCodeblockItemData, - mHruleItemData, - ], - yfm: [ - mHeading1ItemData, - mHeading2ItemData, - mHeading3ItemData, - mHeading4ItemData, - mHeading5ItemData, - mHeading6ItemData, - mBulletListItemData, - mOrderedListItemData, - mLinkButton, - mQuoteButton, - mNoteButton, - mCutButton, - mCodeblockItemData, - mCheckboxButton, - mTableButton, - mImagePopupData, - mHruleItemData, - mFilePopupData, - mTabsItemData, - ], - full: [ - mHeading1ItemData, - mHeading2ItemData, - mHeading3ItemData, - mHeading4ItemData, - mHeading5ItemData, - mHeading6ItemData, - mBulletListItemData, - mOrderedListItemData, - mLinkButton, - mQuoteButton, - mNoteButton, - mCutButton, - mCodeblockItemData, - mCheckboxButton, - mTableButton, - mImagePopupData, - mHruleItemData, - mEmojiItemData, - mFilePopupData, - mTabsItemData, - ], -}; diff --git a/src/bundle/config/wysiwyg.ts b/src/bundle/config/wysiwyg.ts index bc761b6a..e40b31fe 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'; @@ -587,155 +590,6 @@ export const wSelectionMenuConfig: SelectionContextConfig = [ wLinkItemData, ], ]; - -export const wToolbarConfigByPreset: Record = { - zero: [[wUndoItemData, wRedoItemData]], - commonmark: [ - [wUndoItemData, wRedoItemData], - [wBoldItemData, wItalicItemData], - [ - { - id: 'heading', - type: ToolbarDataType.ListButton, - icon: icons.headline, - withArrow: true, - title: i18n.bind(null, 'heading'), - data: [ - wTextItemData, - wHeading1ItemData, - wHeading2ItemData, - wHeading3ItemData, - wHeading4ItemData, - wHeading5ItemData, - wHeading6ItemData, - ], - }, - { - id: 'list', - type: ToolbarDataType.ListButton, - icon: icons.bulletList, - withArrow: true, - title: i18n.bind(null, 'list'), - data: [ - wBulletListItemData, - wOrderedListItemData, - wSinkListItemData, - wLiftListItemData, - ], - }, - wLinkItemData, - wQuoteItemData, - { - id: 'code', - type: ToolbarDataType.ListButton, - icon: icons.code, - withArrow: true, - title: i18n.bind(null, 'code'), - data: [wCodeItemData, wCodeBlockItemData], - }, - ], - ], - default: [ - [wUndoItemData, wRedoItemData], - [wBoldItemData, wItalicItemData, wStrikethroughItemData], - [ - { - id: 'heading', - type: ToolbarDataType.ListButton, - icon: icons.headline, - withArrow: true, - title: i18n.bind(null, 'heading'), - data: [ - wTextItemData, - wHeading1ItemData, - wHeading2ItemData, - wHeading3ItemData, - wHeading4ItemData, - wHeading5ItemData, - wHeading6ItemData, - ], - }, - { - id: 'list', - type: ToolbarDataType.ListButton, - icon: icons.bulletList, - withArrow: true, - title: i18n.bind(null, 'list'), - data: [ - wBulletListItemData, - wOrderedListItemData, - wSinkListItemData, - wLiftListItemData, - ], - }, - wLinkItemData, - wQuoteItemData, - { - id: 'code', - type: ToolbarDataType.ListButton, - icon: icons.code, - withArrow: true, - title: i18n.bind(null, 'code'), - data: [wCodeItemData, wCodeBlockItemData], - }, - ], - ], - yfm: [ - [wUndoItemData, wRedoItemData], - [ - wBoldItemData, - wItalicItemData, - wUnderlineItemData, - wStrikethroughItemData, - wMonospaceItemData, - ], - [ - { - id: 'heading', - type: ToolbarDataType.ListButton, - icon: icons.headline, - withArrow: true, - title: i18n.bind(null, 'heading'), - data: [ - wTextItemData, - wHeading1ItemData, - wHeading2ItemData, - wHeading3ItemData, - wHeading4ItemData, - wHeading5ItemData, - wHeading6ItemData, - ], - }, - { - id: 'list', - type: ToolbarDataType.ListButton, - icon: icons.bulletList, - withArrow: true, - title: i18n.bind(null, 'list'), - data: [ - wBulletListItemData, - wOrderedListItemData, - wSinkListItemData, - wLiftListItemData, - ], - }, - wLinkItemData, - wNoteItemData, - wCutItemData, - wQuoteItemData, - { - id: 'code', - type: ToolbarDataType.ListButton, - icon: icons.code, - withArrow: true, - title: i18n.bind(null, 'code'), - data: [wCodeItemData, wCodeBlockItemData], - }, - ], - [wImageItemData, wFileItemData, wTableItemData, wCheckboxItemData], - ], - full: wToolbarConfig.slice(), -}; export const wCommandMenuConfigByPreset: Record = { zero: [], commonmark: [ @@ -822,13 +676,6 @@ export const wCommandMenuConfigByPreset: Record = { - zero: wCommandMenuConfigByPreset.zero.slice(), - commonmark: wCommandMenuConfigByPreset.commonmark.slice(), - default: wCommandMenuConfigByPreset.default.slice(), - yfm: wCommandMenuConfigByPreset.yfm.slice(), - full: wCommandMenuConfigByPreset.full.slice(), -}; export const wSelectionMenuConfigByPreset: Record = { zero: [], commonmark: [ diff --git a/src/modules/toolbars/constants.ts b/src/modules/toolbars/constants.ts new file mode 100644 index 00000000..f7ea8219 --- /dev/null +++ b/src/modules/toolbars/constants.ts @@ -0,0 +1,34 @@ +const lists = ['heading', 'lists', 'code'] as const; + +type ListsType = L extends readonly (infer T)[] ? T : never; + +const listsObj = lists.reduce, string>>( + (obj, val) => { + obj[val] = val; + return obj; + }, + {} as Record, string>, +); + +export const ListName: Readonly = listsObj; + +const toolbars = [ + 'markupHidden', + 'markupMain', + 'wysiwygHidden', + 'wysiwygMain', + 'wysiwygSelection', + 'wysiwygSlash', +] as const; + +type ToolbarType = L extends readonly (infer T)[] ? T : never; + +const toolbarsObj = toolbars.reduce, string>>( + (obj, val) => { + obj[val] = val; + return obj; + }, + {} as Record, string>, +); + +export const ToolbarName: Readonly = toolbarsObj; diff --git a/src/modules/toolbars/items.tsx b/src/modules/toolbars/items.tsx new file mode 100644 index 00000000..863de022 --- /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', // FIXME: @makhnatkin +}; + +// ---- 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: 'Move list item', // TODO: @makhnatkin add i18n +}; + +// ---- 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..2c1e7726 --- /dev/null +++ b/src/modules/toolbars/presets.ts @@ -0,0 +1,632 @@ +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, + mermaidItemMarkup, + mermaidItemView, + mermaidItemWysiwyg, + 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, + yfmHtmlBlockItemMarkup, + yfmHtmlBlockItemView, + yfmHtmlBlockItemWysiwyg, +} 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.mermaid]: { + view: mermaidItemView, + wysiwyg: mermaidItemWysiwyg, + markup: mermaidItemMarkup, + }, + [Action.htmlBlock]: { + view: yfmHtmlBlockItemView, + wysiwyg: yfmHtmlBlockItemWysiwyg, + markup: yfmHtmlBlockItemMarkup, + }, + [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], + [Action.mermaid, Action.htmlBlock], + ], + [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], + [Action.mermaid, Action.htmlBlock], + ], + [Toolbar.wysiwygHidden]: [[Action.horizontalRule, Action.tabs, Action.emoji]], + [Toolbar.markupHidden]: [[Action.horizontalRule, Action.tabs, Action.emoji]], + }, +}; diff --git a/src/modules/toolbars/types.ts b/src/modules/toolbars/types.ts new file mode 100644 index 00000000..e15a29ca --- /dev/null +++ b/src/modules/toolbars/types.ts @@ -0,0 +1,82 @@ +import {RefObject} from 'react'; + +import {HotkeyProps} from '@gravity-ui/uikit'; +import {EditorState} from 'prosemirror-state'; + +import {ActionStorage} from '../../core'; +import {CodeEditor} from '../../markup'; +import {ToolbarBaseProps, ToolbarDataType, ToolbarIconData} from '../../toolbar'; + +// Items +export type ToolbarItemId = string & {}; +export type ToolbarListId = string & {}; + +export interface ToolbarList { + id: ToolbarListId; + items: ToolbarItemId[]; +} + +export type ToolbarItemView = { + className?: string; + hint?: string | (() => string); + hotkey?: HotkeyProps['value']; + icon?: ToolbarIconData; + title?: string | (() => string); + type?: ToolbarDataType; + doNotActivateList?: boolean; +} & (T extends ToolbarDataType.ListButton + ? { + withArrow?: boolean; + } + : {}); + +export interface EditorActions { + exec(editor: E): void; + isActive(editor: E): boolean; + isEnable(editor: E): boolean; +} + +type ToolbarItemEditor = Partial> & { + hintWhenDisabled?: boolean | string | (() => string); +} & (T extends ToolbarDataType.ButtonPopup + ? { + renderPopup: ( + props: ToolbarBaseProps & { + hide: () => void; + anchorRef: RefObject; + }, + ) => React.ReactNode; + } + : T extends ToolbarDataType.ReactComponent + ? { + width: number; + component: React.ComponentType>; + condition?: ((state: EditorState) => void) | 'enabled'; + } + : {}); + +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; +} + +export type EditorPreset = 'zero' | 'commonmark' | 'default' | 'yfm' | 'full'; + +export type ToolbarsPresetOrEditorPreset = ToolbarsPreset | EditorPreset; 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; From 0acc23bcb385d5f785c4a5a42d9313bb5f2a0678 Mon Sep 17 00:00:00 2001 From: makhnatkin Date: Wed, 18 Dec 2024 14:03:39 +0100 Subject: [PATCH 2/4] feat(toolbars): updated ToolbarItemView type, fixed full preset, updated transformItem --- demo/stories/presets/Preset.tsx | 42 ---------- src/bundle/MarkdownEditorView.tsx | 114 +++----------------------- src/bundle/toolbar/utils.ts | 128 ++++++++++++++++++++++++++++++ src/modules/toolbars/items.tsx | 4 +- src/modules/toolbars/presets.ts | 22 +---- src/modules/toolbars/types.ts | 19 +++-- 6 files changed, 156 insertions(+), 173 deletions(-) create mode 100644 src/bundle/toolbar/utils.ts diff --git a/demo/stories/presets/Preset.tsx b/demo/stories/presets/Preset.tsx index c42a53de..57477476 100644 --- a/demo/stories/presets/Preset.tsx +++ b/demo/stories/presets/Preset.tsx @@ -1,6 +1,5 @@ import React, {CSSProperties, useCallback, useEffect} from 'react'; -import {defaultOptions} from '@diplodoc/transform/lib/sanitize'; import {toaster} from '@gravity-ui/uikit/toaster-singleton-react-18'; import { @@ -12,11 +11,6 @@ import { logger, useMarkdownEditor, } from '../../../src'; -import {FoldingHeading} from '../../../src/extensions/additional/FoldingHeading'; -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 {ToolbarsPreset} from '../../../src/modules/toolbars/types'; import type {FileUploadHandler} from '../../../src/utils/upload'; import {VERSION} from '../../../src/version'; @@ -25,7 +19,6 @@ import {WysiwygSelection} from '../../components/PMSelection'; import {WysiwygDevTools} from '../../components/ProseMirrorDevTools'; import {SplitModePreview} from '../../components/SplitModePreview'; import {plugins} from '../../defaults/md-plugins'; -import useYfmHtmlBlockStyles from '../../hooks/useYfmHtmlBlockStyles'; import {block} from '../../utils/cn'; import {randomDelay} from '../../utils/delay'; import {parseInsertedUrlAsImage} from '../../utils/imageUrl'; @@ -106,41 +99,6 @@ export const Preset = React.memo((props) => { parseInsertedUrlAsImage, }, }, - 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: ` - - , - id = 'unknown', -) => { - if (!item) return null; - const isListButton = item.view.type === ToolbarDataType.ListButton; - - return { - type: item.view.type || 's-button', - 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 = < - T extends WToolbarData | MToolbarData | WToolbarItemData[][] | MToolbarItemData[][], ->( - editorType: 'wysiwyg' | 'markup', - toolbarPreset: ToolbarsPresetOrEditorPreset, - toolbarName: string, -): T => { - const preset = - typeof toolbarPreset === 'string' ? getDefaultPresetByName(toolbarPreset) : toolbarPreset; - const orders = preset.orders[toolbarName] ?? [[]]; - const {items} = preset; - - const toolbarData = orders.map((group) => - group.map((item) => { - return typeof item === 'string' - ? transformItem(editorType, items[item], item) - : { - ...transformItem(editorType, items[item.id], item.id), - data: item.items.map((id) => transformItem(editorType, items[id], id)), - }; - }), - ); - - return toolbarData as T; -}; - -const getDefaultPresetByName = (initialPreset: EditorPreset) => { - const presetName = ['zero', 'commonmark', 'default', 'yfm', 'full'].includes(initialPreset) - ? initialPreset - : 'default'; - return defaultPresets[presetName]; -}; - export const MarkdownEditorView = React.forwardRef( (props, ref) => { const divRef = useEnsuredForwardedRef(ref as MutableRefObject); @@ -150,42 +86,16 @@ export const MarkdownEditorView = React.forwardRef('wysiwyg', toolbarsPreset, ToolbarName.wysiwygMain) - : props.wysiwygToolbarConfig ?? - createConfig('wysiwyg', editor.preset, ToolbarName.wysiwygMain); - - const markupToolbarConfig = toolbarsPreset - ? createConfig('markup', toolbarsPreset, ToolbarName.markupMain) - : props.markupToolbarConfig ?? - createConfig('markup', editor.preset, ToolbarName.markupMain); - - // TODO: @makhnatkin add getToolbarItemDataFromToolbarData - const wysiwygHiddenActionsConfig = toolbarsPreset - ? createConfig( - 'wysiwyg', - toolbarsPreset, - ToolbarName.wysiwygHidden, - )[0] - : props.wysiwygHiddenActionsConfig ?? - createConfig( - 'wysiwyg', - editor.preset, - ToolbarName.wysiwygHidden, - )[0]; - - const markupHiddenActionsConfig = toolbarsPreset - ? createConfig( - 'markup', - toolbarsPreset, - ToolbarName.markupHidden, - )[0] - : props.markupHiddenActionsConfig ?? - createConfig( - 'markup', - editor.preset, - ToolbarName.markupHidden, - )[0]; + const { + wysiwygToolbarConfig, + markupToolbarConfig, + wysiwygHiddenActionsConfig, + markupHiddenActionsConfig, + } = getToolbarsConfigs({ + toolbarsPreset, + props, + preset: editor.preset, + }); const rerender = useUpdate(); React.useLayoutEffect(() => { diff --git a/src/bundle/toolbar/utils.ts b/src/bundle/toolbar/utils.ts new file mode 100644 index 00000000..1fcebad8 --- /dev/null +++ b/src/bundle/toolbar/utils.ts @@ -0,0 +1,128 @@ +import {ToolbarName} from '../../modules/toolbars/constants'; +import {commonmark, defaultPreset, full, yfm, zero} from '../../modules/toolbars/presets'; +import { + EditorPreset, + ToolbarItem, + ToolbarsPresetOrEditorPreset, +} from '../../modules/toolbars/types'; +import { + MToolbarData, + MToolbarItemData, + ToolbarDataType, + WToolbarData, + WToolbarItemData, +} from '../../toolbar'; +import {MarkdownEditorViewProps} from '../MarkdownEditorView'; + +const defaultPresets = { + zero, + commonmark, + default: defaultPreset, + yfm, + full, +}; + +const transformItem = ( + type: 'wysiwyg' | 'markup', + item?: ToolbarItem, + id = 'unknown', +) => { + console.log('item', item, id); + + if (!item) { + console.warn( + `Toolbar item "${id}" not found, it might not have been added to the items dictionary.`, + ); + return {}; + } + + const isListButton = item.view.type === ToolbarDataType.ListButton; + + return { + type: item.view.type || 's-button', + 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: ToolbarsPresetOrEditorPreset, + toolbarName: string, +): T => { + const preset = + typeof toolbarPreset === 'string' ? getDefaultPresetByName(toolbarPreset) : 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 getDefaultPresetByName = (initialPreset: EditorPreset) => { + const presetName = ['zero', 'commonmark', 'default', 'yfm', 'full'].includes(initialPreset) + ? initialPreset + : 'default'; + return defaultPresets[presetName]; +}; + +const flattenPreset = (config: T) => { + // TODO: @makhnatkin add logic for flatten + return (config[0] ?? []) as unknown as T extends WToolbarData + ? WToolbarItemData[] + : MToolbarItemData[]; +}; + +interface GetToolbarsConfigsArgs { + toolbarsPreset?: ToolbarsPresetOrEditorPreset; + props: MarkdownEditorViewProps; + preset: EditorPreset; +} +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/modules/toolbars/items.tsx b/src/modules/toolbars/items.tsx index 863de022..e498142d 100644 --- a/src/modules/toolbars/items.tsx +++ b/src/modules/toolbars/items.tsx @@ -709,7 +709,7 @@ export const toggleHeadingFoldingItemWysiwyg: ToolbarItemWysiwyg = { }; // ---- Text Context ---- -export const textContextItemView: ToolbarItemView = { +export const textContextItemView: ToolbarItemView = { type: ToolbarDataType.ReactComponent, }; export const textContextItemWisywig: ToolbarItemWysiwyg = { @@ -742,7 +742,7 @@ export const paragraphItemMarkup: ToolbarItemMarkup = { }; // --- Colorify ---- -export const colorifyItemView: ToolbarItemView = { +export const colorifyItemView: ToolbarItemView = { type: ToolbarDataType.ReactComponent, }; export const colorifyItemWysiwyg: ToolbarItemWysiwyg = { diff --git a/src/modules/toolbars/presets.ts b/src/modules/toolbars/presets.ts index 2c1e7726..531f8324 100644 --- a/src/modules/toolbars/presets.ts +++ b/src/modules/toolbars/presets.ts @@ -70,9 +70,6 @@ import { markedItemMarkup, markedItemView, markedItemWysiwyg, - mermaidItemMarkup, - mermaidItemView, - mermaidItemWysiwyg, monospaceItemMarkup, monospaceItemView, monospaceItemWysiwyg, @@ -105,9 +102,6 @@ import { undoItemMarkup, undoItemView, undoItemWysiwyg, - yfmHtmlBlockItemMarkup, - yfmHtmlBlockItemView, - yfmHtmlBlockItemWysiwyg, } from './items'; import {ToolbarsPreset} from './types'; @@ -533,16 +527,6 @@ export const full: ToolbarsPreset = { wysiwyg: colorifyItemWysiwyg, markup: colorifyItemMarkup, }, - [Action.mermaid]: { - view: mermaidItemView, - wysiwyg: mermaidItemWysiwyg, - markup: mermaidItemMarkup, - }, - [Action.htmlBlock]: { - view: yfmHtmlBlockItemView, - wysiwyg: yfmHtmlBlockItemWysiwyg, - markup: yfmHtmlBlockItemMarkup, - }, [Action.emoji]: { view: emojiItemView, wysiwyg: emojiItemWysiwyg, @@ -586,7 +570,6 @@ export const full: ToolbarsPreset = { }, ], [Action.image, Action.file, Action.table, Action.checkbox], - [Action.mermaid, Action.htmlBlock], ], [Toolbar.markupMain]: [ [Action.undo, Action.redo], @@ -624,9 +607,8 @@ export const full: ToolbarsPreset = { }, ], [Action.imagePopup, Action.filePopup, Action.table, Action.checkbox], - [Action.mermaid, Action.htmlBlock], ], - [Toolbar.wysiwygHidden]: [[Action.horizontalRule, Action.tabs, Action.emoji]], - [Toolbar.markupHidden]: [[Action.horizontalRule, Action.tabs, Action.emoji]], + [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 index e15a29ca..74c3a4c8 100644 --- a/src/modules/toolbars/types.ts +++ b/src/modules/toolbars/types.ts @@ -20,15 +20,20 @@ export type ToolbarItemView string); hotkey?: HotkeyProps['value']; - icon?: ToolbarIconData; - title?: string | (() => string); type?: ToolbarDataType; doNotActivateList?: boolean; -} & (T extends ToolbarDataType.ListButton +} & (T extends ToolbarDataType.SingleButton ? { - withArrow?: boolean; + icon: ToolbarIconData; + title: string | (() => string); } - : {}); + : T extends ToolbarDataType.ListButton + ? { + withArrow?: boolean; + icon: ToolbarIconData; + title: string | (() => string); + } + : {}); export interface EditorActions { exec(editor: E): void; @@ -60,12 +65,12 @@ export type ToolbarItemWysiwyg = ToolbarItemEditor; -export type ToolbarItem = { +export type ToolbarItem = { view: ToolbarItemView; wysiwyg?: ToolbarItemWysiwyg; markup?: ToolbarItemMarkup; }; -export type ToolbarsItems = Record; +export type ToolbarsItems = Record>; // Orders export type ToolbarId = string; From e29ff333d74a160b9342553de98fecd5d5f26364 Mon Sep 17 00:00:00 2001 From: makhnatkin Date: Thu, 19 Dec 2024 10:00:06 +0100 Subject: [PATCH 3/4] feat(toolbars): fixed types --- src/bundle/MarkdownEditorView.tsx | 4 +- src/bundle/config/markup.tsx | 207 ++++++++++++++++++++++++++++++ src/bundle/config/wysiwyg.ts | 156 ++++++++++++++++++++++ src/bundle/toolbar/utils.ts | 62 +++++---- src/modules/toolbars/constants.ts | 48 ++----- src/modules/toolbars/items.tsx | 2 +- src/modules/toolbars/types.ts | 16 +-- 7 files changed, 422 insertions(+), 73 deletions(-) diff --git a/src/bundle/MarkdownEditorView.tsx b/src/bundle/MarkdownEditorView.tsx index 101ddfb1..edd65e62 100644 --- a/src/bundle/MarkdownEditorView.tsx +++ b/src/bundle/MarkdownEditorView.tsx @@ -7,7 +7,7 @@ import {useEnsuredForwardedRef, useKey, useUpdate} from 'react-use'; import {ClassNameProps, cn} from '../classname'; import {i18n} from '../i18n/bundle'; import {logger} from '../logger'; -import {ToolbarsPresetOrEditorPreset} from '../modules/toolbars/types'; +import type {ToolbarsPreset} from '../modules/toolbars/types'; import {ToasterContext, useBooleanState, useSticky} from '../react-utils'; import {isMac} from '../utils'; @@ -32,7 +32,7 @@ const b = cnEditorComponent; export type MarkdownEditorViewProps = ClassNameProps & { editor?: Editor; autofocus?: boolean; - toolbarsPreset?: ToolbarsPresetOrEditorPreset; + toolbarsPreset?: ToolbarsPreset; /** * @deprecated use `toolbarsPreset` instead */ diff --git a/src/bundle/config/markup.tsx b/src/bundle/config/markup.tsx index c8babf3d..b6835be9 100644 --- a/src/bundle/config/markup.tsx +++ b/src/bundle/config/markup.tsx @@ -57,6 +57,7 @@ import { import {MToolbarColors} from '../toolbar/markup/MToolbarColors'; import {MToolbarFilePopup} from '../toolbar/markup/MToolbarFilePopup'; import {MToolbarImagePopup} from '../toolbar/markup/MToolbarImagePopup'; +import type {MarkdownEditorPreset} from '../types'; import {ActionName} from './action-names'; import {icons} from './icons'; @@ -544,3 +545,209 @@ export const mToolbarConfig: MToolbarData = [ ], [mImagePopupData, mFilePopupData, mTableButton, mCheckboxButton], ]; + +export const mToolbarConfigByPreset: Record = { + zero: [mHistoryGroupConfig], + commonmark: [ + [mUndoItemData, mRedoItemData], + [mBoldItemData, mItalicItemData], + [ + { + id: 'heading', + type: ToolbarDataType.ListButton, + icon: icons.headline, + withArrow: true, + title: i18n.bind(null, 'heading'), + data: [ + mHeading1ItemData, + mHeading2ItemData, + mHeading3ItemData, + mHeading4ItemData, + mHeading5ItemData, + mHeading6ItemData, + ], + }, + { + id: 'list', + type: ToolbarDataType.ListButton, + icon: icons.bulletList, + withArrow: true, + title: i18n.bind(null, 'list'), + data: [mBulletListItemData, mOrderedListItemData], + }, + mLinkButton, + mQuoteButton, + { + id: 'code', + type: ToolbarDataType.ListButton, + icon: icons.code, + withArrow: true, + title: i18n.bind(null, 'code'), + data: [mCodeItemData, mCodeblockItemData], + }, + ], + ], + default: [ + [mUndoItemData, mRedoItemData], + [mBoldItemData, mItalicItemData, mStrikethroughItemData], + [ + { + id: 'heading', + type: ToolbarDataType.ListButton, + icon: icons.headline, + withArrow: true, + title: i18n.bind(null, 'heading'), + data: [ + mHeading1ItemData, + mHeading2ItemData, + mHeading3ItemData, + mHeading4ItemData, + mHeading5ItemData, + mHeading6ItemData, + ], + }, + { + id: 'list', + type: ToolbarDataType.ListButton, + icon: icons.bulletList, + withArrow: true, + title: i18n.bind(null, 'list'), + data: [mBulletListItemData, mOrderedListItemData], + }, + mLinkButton, + mQuoteButton, + { + id: 'code', + type: ToolbarDataType.ListButton, + icon: icons.code, + withArrow: true, + title: i18n.bind(null, 'code'), + data: [mCodeItemData, mCodeblockItemData], + }, + ], + ], + yfm: [ + [mUndoItemData, mRedoItemData], + [ + mBoldItemData, + mItalicItemData, + mUnderlineItemData, + mStrikethroughItemData, + mMonospaceItemData, + ], + [ + { + id: 'heading', + type: ToolbarDataType.ListButton, + icon: icons.headline, + withArrow: true, + title: i18n.bind(null, 'heading'), + data: [ + mHeading1ItemData, + mHeading2ItemData, + mHeading3ItemData, + mHeading4ItemData, + mHeading5ItemData, + mHeading6ItemData, + ], + }, + { + id: 'list', + type: ToolbarDataType.ListButton, + icon: icons.bulletList, + withArrow: true, + title: i18n.bind(null, 'list'), + data: [mBulletListItemData, mOrderedListItemData], + }, + mLinkButton, + mNoteButton, + mCutButton, + mQuoteButton, + { + id: 'code', + type: ToolbarDataType.ListButton, + icon: icons.code, + withArrow: true, + title: i18n.bind(null, 'code'), + data: [mCodeItemData, mCodeblockItemData], + }, + ], + [mImagePopupData, mFilePopupData, mTableButton, mCheckboxButton], + ], + full: mToolbarConfig.slice(), +}; + +export const mHiddenDataByPreset: Record = { + zero: [], + commonmark: [ + mHeading1ItemData, + mHeading2ItemData, + mHeading3ItemData, + mHeading4ItemData, + mHeading5ItemData, + mHeading6ItemData, + mBulletListItemData, + mOrderedListItemData, + mLinkButton, + mQuoteButton, + mCodeblockItemData, + mHruleItemData, + ], + default: [ + mHeading1ItemData, + mHeading2ItemData, + mHeading3ItemData, + mHeading4ItemData, + mHeading5ItemData, + mHeading6ItemData, + mBulletListItemData, + mOrderedListItemData, + mLinkButton, + mQuoteButton, + mCodeblockItemData, + mHruleItemData, + ], + yfm: [ + mHeading1ItemData, + mHeading2ItemData, + mHeading3ItemData, + mHeading4ItemData, + mHeading5ItemData, + mHeading6ItemData, + mBulletListItemData, + mOrderedListItemData, + mLinkButton, + mQuoteButton, + mNoteButton, + mCutButton, + mCodeblockItemData, + mCheckboxButton, + mTableButton, + mImagePopupData, + mHruleItemData, + mFilePopupData, + mTabsItemData, + ], + full: [ + mHeading1ItemData, + mHeading2ItemData, + mHeading3ItemData, + mHeading4ItemData, + mHeading5ItemData, + mHeading6ItemData, + mBulletListItemData, + mOrderedListItemData, + mLinkButton, + mQuoteButton, + mNoteButton, + mCutButton, + mCodeblockItemData, + mCheckboxButton, + mTableButton, + mImagePopupData, + mHruleItemData, + mEmojiItemData, + mFilePopupData, + mTabsItemData, + ], +}; diff --git a/src/bundle/config/wysiwyg.ts b/src/bundle/config/wysiwyg.ts index e40b31fe..db9d1a38 100644 --- a/src/bundle/config/wysiwyg.ts +++ b/src/bundle/config/wysiwyg.ts @@ -590,6 +590,155 @@ export const wSelectionMenuConfig: SelectionContextConfig = [ wLinkItemData, ], ]; + +export const wToolbarConfigByPreset: Record = { + zero: [[wUndoItemData, wRedoItemData]], + commonmark: [ + [wUndoItemData, wRedoItemData], + [wBoldItemData, wItalicItemData], + [ + { + id: 'heading', + type: ToolbarDataType.ListButton, + icon: icons.headline, + withArrow: true, + title: i18n.bind(null, 'heading'), + data: [ + wTextItemData, + wHeading1ItemData, + wHeading2ItemData, + wHeading3ItemData, + wHeading4ItemData, + wHeading5ItemData, + wHeading6ItemData, + ], + }, + { + id: 'list', + type: ToolbarDataType.ListButton, + icon: icons.bulletList, + withArrow: true, + title: i18n.bind(null, 'list'), + data: [ + wBulletListItemData, + wOrderedListItemData, + wSinkListItemData, + wLiftListItemData, + ], + }, + wLinkItemData, + wQuoteItemData, + { + id: 'code', + type: ToolbarDataType.ListButton, + icon: icons.code, + withArrow: true, + title: i18n.bind(null, 'code'), + data: [wCodeItemData, wCodeBlockItemData], + }, + ], + ], + default: [ + [wUndoItemData, wRedoItemData], + [wBoldItemData, wItalicItemData, wStrikethroughItemData], + [ + { + id: 'heading', + type: ToolbarDataType.ListButton, + icon: icons.headline, + withArrow: true, + title: i18n.bind(null, 'heading'), + data: [ + wTextItemData, + wHeading1ItemData, + wHeading2ItemData, + wHeading3ItemData, + wHeading4ItemData, + wHeading5ItemData, + wHeading6ItemData, + ], + }, + { + id: 'list', + type: ToolbarDataType.ListButton, + icon: icons.bulletList, + withArrow: true, + title: i18n.bind(null, 'list'), + data: [ + wBulletListItemData, + wOrderedListItemData, + wSinkListItemData, + wLiftListItemData, + ], + }, + wLinkItemData, + wQuoteItemData, + { + id: 'code', + type: ToolbarDataType.ListButton, + icon: icons.code, + withArrow: true, + title: i18n.bind(null, 'code'), + data: [wCodeItemData, wCodeBlockItemData], + }, + ], + ], + yfm: [ + [wUndoItemData, wRedoItemData], + [ + wBoldItemData, + wItalicItemData, + wUnderlineItemData, + wStrikethroughItemData, + wMonospaceItemData, + ], + [ + { + id: 'heading', + type: ToolbarDataType.ListButton, + icon: icons.headline, + withArrow: true, + title: i18n.bind(null, 'heading'), + data: [ + wTextItemData, + wHeading1ItemData, + wHeading2ItemData, + wHeading3ItemData, + wHeading4ItemData, + wHeading5ItemData, + wHeading6ItemData, + ], + }, + { + id: 'list', + type: ToolbarDataType.ListButton, + icon: icons.bulletList, + withArrow: true, + title: i18n.bind(null, 'list'), + data: [ + wBulletListItemData, + wOrderedListItemData, + wSinkListItemData, + wLiftListItemData, + ], + }, + wLinkItemData, + wNoteItemData, + wCutItemData, + wQuoteItemData, + { + id: 'code', + type: ToolbarDataType.ListButton, + icon: icons.code, + withArrow: true, + title: i18n.bind(null, 'code'), + data: [wCodeItemData, wCodeBlockItemData], + }, + ], + [wImageItemData, wFileItemData, wTableItemData, wCheckboxItemData], + ], + full: wToolbarConfig.slice(), +}; export const wCommandMenuConfigByPreset: Record = { zero: [], commonmark: [ @@ -676,6 +825,13 @@ export const wCommandMenuConfigByPreset: Record = { + zero: wCommandMenuConfigByPreset.zero.slice(), + commonmark: wCommandMenuConfigByPreset.commonmark.slice(), + default: wCommandMenuConfigByPreset.default.slice(), + yfm: wCommandMenuConfigByPreset.yfm.slice(), + full: wCommandMenuConfigByPreset.full.slice(), +}; export const wSelectionMenuConfigByPreset: Record = { zero: [], commonmark: [ diff --git a/src/bundle/toolbar/utils.ts b/src/bundle/toolbar/utils.ts index 1fcebad8..ebd96f7f 100644 --- a/src/bundle/toolbar/utils.ts +++ b/src/bundle/toolbar/utils.ts @@ -1,20 +1,17 @@ import {ToolbarName} from '../../modules/toolbars/constants'; import {commonmark, defaultPreset, full, yfm, zero} from '../../modules/toolbars/presets'; -import { +import type { EditorPreset, ToolbarItem, - ToolbarsPresetOrEditorPreset, + ToolbarItemMarkup, + ToolbarItemWysiwyg, + ToolbarsPreset, } from '../../modules/toolbars/types'; -import { - MToolbarData, - MToolbarItemData, - ToolbarDataType, - WToolbarData, - WToolbarItemData, -} from '../../toolbar'; -import {MarkdownEditorViewProps} from '../MarkdownEditorView'; +import type {MToolbarData, MToolbarItemData, WToolbarData, WToolbarItemData} from '../../toolbar'; +import {ToolbarDataType, ToolbarIconData} from '../../toolbar'; +import type {MarkdownEditorViewProps} from '../MarkdownEditorView'; -const defaultPresets = { +const defaultPresets: Record = { zero, commonmark, default: defaultPreset, @@ -22,24 +19,34 @@ const defaultPresets = { 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', -) => { - console.log('item', item, id); - +): TransformedItem => { if (!item) { console.warn( `Toolbar item "${id}" not found, it might not have been added to the items dictionary.`, ); - return {}; + return {} as TransformedItem; } const isListButton = item.view.type === ToolbarDataType.ListButton; return { - type: item.view.type || 's-button', + type: item.view.type ?? ToolbarDataType.SingleButton, id, title: item.view.title, hint: item.view.hint, @@ -53,11 +60,13 @@ const transformItem = ( export const createConfig = ( editorType: 'wysiwyg' | 'markup', - toolbarPreset: ToolbarsPresetOrEditorPreset, + toolbarPreset: ToolbarsPreset | EditorPreset, toolbarName: string, ): T => { const preset = - typeof toolbarPreset === 'string' ? getDefaultPresetByName(toolbarPreset) : toolbarPreset; + typeof toolbarPreset === 'string' + ? defaultPresets[toolbarPreset] || defaultPresets.default + : toolbarPreset; const orders = preset.orders[toolbarName] ?? [[]]; const {items} = preset; @@ -75,13 +84,6 @@ export const createConfig = ( return toolbarData as T; }; -const getDefaultPresetByName = (initialPreset: EditorPreset) => { - const presetName = ['zero', 'commonmark', 'default', 'yfm', 'full'].includes(initialPreset) - ? initialPreset - : 'default'; - return defaultPresets[presetName]; -}; - const flattenPreset = (config: T) => { // TODO: @makhnatkin add logic for flatten return (config[0] ?? []) as unknown as T extends WToolbarData @@ -90,8 +92,14 @@ const flattenPreset = (config: T) => { }; interface GetToolbarsConfigsArgs { - toolbarsPreset?: ToolbarsPresetOrEditorPreset; - props: MarkdownEditorViewProps; + toolbarsPreset?: ToolbarsPreset; + props: Pick< + MarkdownEditorViewProps, + | 'markupToolbarConfig' + | 'wysiwygToolbarConfig' + | 'wysiwygHiddenActionsConfig' + | 'markupHiddenActionsConfig' + >; preset: EditorPreset; } export const getToolbarsConfigs = ({toolbarsPreset, props, preset}: GetToolbarsConfigsArgs) => { diff --git a/src/modules/toolbars/constants.ts b/src/modules/toolbars/constants.ts index f7ea8219..1b7cd315 100644 --- a/src/modules/toolbars/constants.ts +++ b/src/modules/toolbars/constants.ts @@ -1,34 +1,14 @@ -const lists = ['heading', 'lists', 'code'] as const; - -type ListsType = L extends readonly (infer T)[] ? T : never; - -const listsObj = lists.reduce, string>>( - (obj, val) => { - obj[val] = val; - return obj; - }, - {} as Record, string>, -); - -export const ListName: Readonly = listsObj; - -const toolbars = [ - 'markupHidden', - 'markupMain', - 'wysiwygHidden', - 'wysiwygMain', - 'wysiwygSelection', - 'wysiwygSlash', -] as const; - -type ToolbarType = L extends readonly (infer T)[] ? T : never; - -const toolbarsObj = toolbars.reduce, string>>( - (obj, val) => { - obj[val] = val; - return obj; - }, - {} as Record, string>, -); - -export const ToolbarName: Readonly = toolbarsObj; +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 index e498142d..62620fb3 100644 --- a/src/modules/toolbars/items.tsx +++ b/src/modules/toolbars/items.tsx @@ -705,7 +705,7 @@ 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', // FIXME: @makhnatkin + condition: 'enabled', }; // ---- Text Context ---- diff --git a/src/modules/toolbars/types.ts b/src/modules/toolbars/types.ts index 74c3a4c8..d32aec18 100644 --- a/src/modules/toolbars/types.ts +++ b/src/modules/toolbars/types.ts @@ -1,11 +1,11 @@ -import {RefObject} from 'react'; +import type {RefObject} from 'react'; -import {HotkeyProps} from '@gravity-ui/uikit'; -import {EditorState} from 'prosemirror-state'; +import type {HotkeyProps} from '@gravity-ui/uikit'; +import type {EditorState} from 'prosemirror-state'; -import {ActionStorage} from '../../core'; -import {CodeEditor} from '../../markup'; -import {ToolbarBaseProps, ToolbarDataType, ToolbarIconData} from '../../toolbar'; +import type {ActionStorage} from '../../core'; +import type {CodeEditor} from '../../markup'; +import type {ToolbarBaseProps, ToolbarDataType, ToolbarIconData} from '../../toolbar'; // Items export type ToolbarItemId = string & {}; @@ -43,6 +43,7 @@ export interface EditorActions { type ToolbarItemEditor = Partial> & { hintWhenDisabled?: boolean | string | (() => string); + condition?: ((state: EditorState) => void) | 'enabled'; } & (T extends ToolbarDataType.ButtonPopup ? { renderPopup: ( @@ -56,7 +57,6 @@ type ToolbarItemEditor = Partial> & { ? { width: number; component: React.ComponentType>; - condition?: ((state: EditorState) => void) | 'enabled'; } : {}); @@ -83,5 +83,3 @@ export interface ToolbarsPreset { } export type EditorPreset = 'zero' | 'commonmark' | 'default' | 'yfm' | 'full'; - -export type ToolbarsPresetOrEditorPreset = ToolbarsPreset | EditorPreset; From f0524e0f14d6525e2dfe1349e4c12babcf49b5ac Mon Sep 17 00:00:00 2001 From: makhnatkin Date: Thu, 19 Dec 2024 11:28:13 +0100 Subject: [PATCH 4/4] feat(toolbars): fixed types, i18n, memo --- src/bundle/MarkdownEditorView.tsx | 30 +++++++++++++++++++++++++----- src/bundle/toolbar/utils.ts | 8 ++++---- src/i18n/menubar/en.json | 1 + src/i18n/menubar/ru.json | 1 + src/modules/toolbars/items.tsx | 2 +- src/modules/toolbars/types.ts | 5 +++-- 6 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/bundle/MarkdownEditorView.tsx b/src/bundle/MarkdownEditorView.tsx index edd65e62..d0fe9d6f 100644 --- a/src/bundle/MarkdownEditorView.tsx +++ b/src/bundle/MarkdownEditorView.tsx @@ -82,6 +82,10 @@ 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(() => { diff --git a/src/bundle/toolbar/utils.ts b/src/bundle/toolbar/utils.ts index ebd96f7f..94447bc1 100644 --- a/src/bundle/toolbar/utils.ts +++ b/src/bundle/toolbar/utils.ts @@ -1,7 +1,6 @@ import {ToolbarName} from '../../modules/toolbars/constants'; import {commonmark, defaultPreset, full, yfm, zero} from '../../modules/toolbars/presets'; import type { - EditorPreset, ToolbarItem, ToolbarItemMarkup, ToolbarItemWysiwyg, @@ -10,8 +9,9 @@ import type { 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 = { +const defaultPresets: Record = { zero, commonmark, default: defaultPreset, @@ -60,7 +60,7 @@ const transformItem = ( export const createConfig = ( editorType: 'wysiwyg' | 'markup', - toolbarPreset: ToolbarsPreset | EditorPreset, + toolbarPreset: ToolbarsPreset | MarkdownEditorPreset, toolbarName: string, ): T => { const preset = @@ -100,7 +100,7 @@ interface GetToolbarsConfigsArgs { | 'wysiwygHiddenActionsConfig' | 'markupHiddenActionsConfig' >; - preset: EditorPreset; + preset: MarkdownEditorPreset; } export const getToolbarsConfigs = ({toolbarsPreset, props, preset}: GetToolbarsConfigsArgs) => { const wysiwygToolbarConfig = toolbarsPreset 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/items.tsx b/src/modules/toolbars/items.tsx index 62620fb3..80331f1d 100644 --- a/src/modules/toolbars/items.tsx +++ b/src/modules/toolbars/items.tsx @@ -793,7 +793,7 @@ export const moveListItemView: ToolbarItemView = { type: ToolbarDataType.ListButton, icon: icons.lift, withArrow: true, - title: 'Move list item', // TODO: @makhnatkin add i18n + title: i18n.bind(null, 'move_list'), }; // ---- Code list ---- diff --git a/src/modules/toolbars/types.ts b/src/modules/toolbars/types.ts index d32aec18..ec3e2de6 100644 --- a/src/modules/toolbars/types.ts +++ b/src/modules/toolbars/types.ts @@ -16,6 +16,9 @@ export interface ToolbarList { items: ToolbarItemId[]; } +/** + * The default value for the `type` property is `ToolbarDataType.SingleButton`. + */ export type ToolbarItemView = { className?: string; hint?: string | (() => string); @@ -81,5 +84,3 @@ export interface ToolbarsPreset { items: ToolbarsItems; orders: ToolbarsOrders; } - -export type EditorPreset = 'zero' | 'commonmark' | 'default' | 'yfm' | 'full';