From 5e0666bb6dc4e813f157712953f6e80dc43e240f Mon Sep 17 00:00:00 2001 From: Yuriy Demidov Date: Tue, 5 May 2026 20:23:17 +0300 Subject: [PATCH 1/2] feat(YfmTable): add background color picker to row and column controls --- demo/src/components/Playground.tsx | 1 + demo/src/hocs/withLang.tsx | 1 + demo/src/hocs/withThemeProvider.tsx | 1 + demo/src/hocs/withToaster.tsx | 1 + .../extensions/yfm/YfmTable/YfmTable.test.ts | 82 +++++++++++++++++++ .../yfm/YfmTable/YfmTableSpecs/const.ts | 1 + .../yfm/YfmTable/YfmTableSpecs/schema.ts | 1 + .../yfm/YfmTable/YfmTableSpecs/serializer.ts | 9 +- .../src/extensions/yfm/YfmTable/index.ts | 14 +++- .../YfmTableControls/commands/set-cell-bg.ts | 49 +++++++++++ .../CellBgPalette/CellBgPalette.scss | 62 ++++++++++++++ .../CellBgPalette/CellBgPalette.tsx | 58 +++++++++++++ .../components/CellBgPalette/colors.ts | 11 +++ .../FloatingMenuControl.tsx | 33 ++++++++ .../plugins/YfmTableControls/dnd/dnd.scss | 5 +- .../plugins/YfmTableControls/index.ts | 9 +- .../nodeviews/yfm-table-cell-view.tsx | 55 +++++++++++++ .../YfmTableControls/plugins/focus-plugin.ts | 2 +- packages/editor/src/i18n/yfm-table/en.json | 9 ++ packages/editor/src/i18n/yfm-table/ru.json | 9 ++ packages/editor/src/styles/markdown.scss | 1 + packages/editor/src/styles/yc-table.scss | 23 ++++++ 22 files changed, 428 insertions(+), 9 deletions(-) create mode 100644 packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/commands/set-cell-bg.ts create mode 100644 packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/components/CellBgPalette/CellBgPalette.scss create mode 100644 packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/components/CellBgPalette/CellBgPalette.tsx create mode 100644 packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/components/CellBgPalette/colors.ts create mode 100644 packages/editor/src/styles/yc-table.scss diff --git a/demo/src/components/Playground.tsx b/demo/src/components/Playground.tsx index 3bfa7b486..46a370d94 100644 --- a/demo/src/components/Playground.tsx +++ b/demo/src/components/Playground.tsx @@ -259,6 +259,7 @@ export const Playground = memo((props) => { table_ignoreSplittersInBlockMath: true, table_ignoreSplittersInInlineCode: true, table_ignoreSplittersInInlineMath: true, + cellBackground: true, }, ...wysiwygConfig?.extensionOptions, }, diff --git a/demo/src/hocs/withLang.tsx b/demo/src/hocs/withLang.tsx index 4fab8d240..ea343c0d1 100644 --- a/demo/src/hocs/withLang.tsx +++ b/demo/src/hocs/withLang.tsx @@ -3,6 +3,7 @@ import {configure} from '@gravity-ui/uikit'; import type {Decorator} from '@storybook/react'; import '@gravity-ui/uikit/styles/styles.scss'; +import '@gravity-ui/markdown-editor/styles/markdown.css'; // eslint-disable-line import/order export const withLang: Decorator = (StoryItem, context) => { const lang = context.globals.lang; diff --git a/demo/src/hocs/withThemeProvider.tsx b/demo/src/hocs/withThemeProvider.tsx index 48c010ea1..afda74626 100644 --- a/demo/src/hocs/withThemeProvider.tsx +++ b/demo/src/hocs/withThemeProvider.tsx @@ -2,6 +2,7 @@ import {ThemeProvider} from '@gravity-ui/uikit'; import type {Decorator} from '@storybook/react'; import '@gravity-ui/uikit/styles/styles.scss'; +import '@gravity-ui/markdown-editor/styles/markdown.css'; // eslint-disable-line import/order export const withThemeProvider: Decorator = (StoryItem, context) => { return ( diff --git a/demo/src/hocs/withToaster.tsx b/demo/src/hocs/withToaster.tsx index fa7bb4055..40948f5cc 100644 --- a/demo/src/hocs/withToaster.tsx +++ b/demo/src/hocs/withToaster.tsx @@ -3,6 +3,7 @@ import {toaster} from '@gravity-ui/uikit/toaster-singleton'; import type {Decorator} from '@storybook/react'; import '@gravity-ui/uikit/styles/styles.scss'; +import '@gravity-ui/markdown-editor/styles/markdown.css'; // eslint-disable-line import/order export const withToaster: Decorator = (StoryItem, context) => { return ( diff --git a/packages/editor/src/extensions/yfm/YfmTable/YfmTable.test.ts b/packages/editor/src/extensions/yfm/YfmTable/YfmTable.test.ts index 82afa32da..4b3c4c5b9 100644 --- a/packages/editor/src/extensions/yfm/YfmTable/YfmTable.test.ts +++ b/packages/editor/src/extensions/yfm/YfmTable/YfmTable.test.ts @@ -1,6 +1,7 @@ import {EditorState} from 'prosemirror-state'; import {builders} from 'prosemirror-test-builder'; import {EditorView} from 'prosemirror-view'; +import dd from 'ts-dedent'; import {dispatchPasteEvent} from '../../../../tests/dispatch-event'; import {parseDOM} from '../../../../tests/parse-dom'; @@ -519,6 +520,87 @@ nested table ); }); + // TODO: enable when @diplodoc/transform >= 4.75.0 is released (parses ::{bg=...} cell attrs) + it.skip('should serialize cell-bg on first cell (same line as ||)', () => { + const markup = dd` + #| + || ::{bg="info"} + + cell11 + + || + |# + + `.trimStart(); + + same( + markup, + doc(table(tbody(tr(td({[YfmTableAttr.CellBg]: 'info'}, p('cell11'), p('')))))), + ); + }); + + it.skip('should serialize cell-bg on non-first cell (same line as |)', () => { + const markup = dd` + #| + || + + cell11 + + |::{bg="warning"} + + cell12 + + || + |# + + `.trimStart(); + + same( + markup, + doc( + table( + tbody( + tr( + td(p('cell11'), p('')), + td({[YfmTableAttr.CellBg]: 'warning'}, p('cell12'), p('')), + ), + ), + ), + ), + ); + }); + + it.skip('should serialize cell-bg on multiple cells', () => { + const markup = dd` + #| + || ::{bg="info"} + + cell11 + + |::{bg="danger"} + + cell12 + + || + |# + + `.trimStart(); + + same( + markup, + doc( + table( + tbody( + tr( + td({[YfmTableAttr.CellBg]: 'info'}, p('cell11'), p('')), + td({[YfmTableAttr.CellBg]: 'danger'}, p('cell12'), p('')), + ), + ), + ), + ), + ); + }); + it('should preserve cell-align', () => { const markup = ` #| diff --git a/packages/editor/src/extensions/yfm/YfmTable/YfmTableSpecs/const.ts b/packages/editor/src/extensions/yfm/YfmTable/YfmTableSpecs/const.ts index 9c47a488c..c590665c2 100644 --- a/packages/editor/src/extensions/yfm/YfmTable/YfmTableSpecs/const.ts +++ b/packages/editor/src/extensions/yfm/YfmTable/YfmTableSpecs/const.ts @@ -9,4 +9,5 @@ export enum YfmTableAttr { Colspan = 'colspan', Rowspan = 'rowspan', CellAlign = 'data-cell-align', + CellBg = 'data-bg', } diff --git a/packages/editor/src/extensions/yfm/YfmTable/YfmTableSpecs/schema.ts b/packages/editor/src/extensions/yfm/YfmTable/YfmTableSpecs/schema.ts index 7866428d6..10b807291 100644 --- a/packages/editor/src/extensions/yfm/YfmTable/YfmTableSpecs/schema.ts +++ b/packages/editor/src/extensions/yfm/YfmTable/YfmTableSpecs/schema.ts @@ -76,6 +76,7 @@ export const getSchemaSpecs = ( [YfmTableAttr.Colspan]: {default: null}, [YfmTableAttr.Rowspan]: {default: null}, [YfmTableAttr.CellAlign]: {default: null}, + [YfmTableAttr.CellBg]: {default: null}, }, parseDOM: [ {tag: 'td', priority: 200}, diff --git a/packages/editor/src/extensions/yfm/YfmTable/YfmTableSpecs/serializer.ts b/packages/editor/src/extensions/yfm/YfmTable/YfmTableSpecs/serializer.ts index 9d2bb0e7b..df633ccf5 100644 --- a/packages/editor/src/extensions/yfm/YfmTable/YfmTableSpecs/serializer.ts +++ b/packages/editor/src/extensions/yfm/YfmTable/YfmTableSpecs/serializer.ts @@ -20,7 +20,10 @@ export const serializerTokens: Record = { const rowspanStack: Record = {}; tbody.forEach((trow) => { - state.write('||'); + const firstCellBg = trow.firstChild?.attrs[YfmTableAttr.CellBg]; + const firstCellAttrs = + typeof firstCellBg === 'string' ? ` ::{bg="${firstCellBg}"}` : ''; + state.write(`||${firstCellAttrs}`); state.ensureNewLine(); state.write('\n'); @@ -39,7 +42,9 @@ export const serializerTokens: Record = { } if (colIndex > 0) { - state.write('|'); + const cellBg = td.attrs[YfmTableAttr.CellBg]; + const cellAttrs = typeof cellBg === 'string' ? `::{bg="${cellBg}"}` : ''; + state.write(cellAttrs ? `|${cellAttrs}` : '|'); state.ensureNewLine(); state.write('\n'); } diff --git a/packages/editor/src/extensions/yfm/YfmTable/index.ts b/packages/editor/src/extensions/yfm/YfmTable/index.ts index 7d3051935..769170da4 100644 --- a/packages/editor/src/extensions/yfm/YfmTable/index.ts +++ b/packages/editor/src/extensions/yfm/YfmTable/index.ts @@ -31,6 +31,13 @@ export type YfmTableOptions = YfmTableSpecsOptions & { * @default true */ dnd?: boolean; + /** + * Enables cell background color picker for table cells. + * Available with @diplodoc/transform v4.75.0-beta0 or higher. + * @default false + */ + // TODO [MAJOR]: enable by default and remove option + cellBackground?: boolean; }; export const YfmTable: ExtensionWithOptions = (builder, options) => { @@ -47,7 +54,12 @@ export const YfmTable: ExtensionWithOptions = (builder, options builder.addPlugin(yfmTableTransformPastedPlugin); if (options.controls !== false) { - builder.addPlugin(yfmTableControlsPlugins({dndEnabled: options.dnd !== false})); + builder.addPlugin( + yfmTableControlsPlugins({ + dndEnabled: options.dnd !== false, + cellBackground: options.cellBackground === true, + }), + ); } }; diff --git a/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/commands/set-cell-bg.ts b/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/commands/set-cell-bg.ts new file mode 100644 index 000000000..27e3ab92a --- /dev/null +++ b/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/commands/set-cell-bg.ts @@ -0,0 +1,49 @@ +import type {Command} from '#pm/state'; +import {TableDesc} from 'src/table-utils/table-desc'; + +import {YfmTableAttr} from '../../../YfmTableSpecs/const'; + +export type SetCellBgParams = { + tablePos: number; + rows?: number[]; + cols?: number[]; + bg: string | null; +}; + +export const setCellBg = (params: SetCellBgParams): Command => { + return (state, dispatch) => { + const table = state.doc.nodeAt(params.tablePos); + const tableDesc = table && TableDesc.create(table)?.bind(params.tablePos); + if (!tableDesc) return false; + + if (!dispatch) return true; + + const {tr} = state; + + const apply = (cellPos: ReturnType[number]) => { + if (cellPos.type !== 'real') return; + const node = state.doc.nodeAt(cellPos.from); + if (!node) return; + tr.setNodeAttribute( + tr.mapping.map(cellPos.from), + YfmTableAttr.CellBg, + params.bg || null, + ); + }; + + if (params.rows) { + for (const rowIdx of params.rows) { + for (const pos of tableDesc.getPosForRowCells(rowIdx)) apply(pos); + } + } + if (params.cols) { + for (const colIdx of params.cols) { + for (const pos of tableDesc.getPosForColumn(colIdx)) apply(pos); + } + } + + if (tr.docChanged) dispatch(tr); + + return true; + }; +}; diff --git a/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/components/CellBgPalette/CellBgPalette.scss b/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/components/CellBgPalette/CellBgPalette.scss new file mode 100644 index 000000000..4b6822d8b --- /dev/null +++ b/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/components/CellBgPalette/CellBgPalette.scss @@ -0,0 +1,62 @@ +.g-md-yfm-table-cell-bg-palette { + &__swatch { + display: block; + + width: 16px; + height: 16px; + + border: 1px solid var(--g-color-line-generic); + border-radius: var(--g-border-radius-xs); + + &_none { + position: relative; + + overflow: hidden; + + background-color: transparent; + + &::after { + position: absolute; + inset: 0; + + content: ''; + + background: linear-gradient( + to bottom right, + transparent calc(50% - 1px), + var(--g-color-line-danger) calc(50% - 1px), + var(--g-color-line-danger) calc(50% + 1px), + transparent calc(50% + 1px) + ); + } + } + + &_color_info { + background-color: var(--g-color-base-info-medium); + } + + &_color_positive { + background-color: var(--g-color-base-positive-medium); + } + + &_color_warning { + background-color: var(--g-color-base-warning-medium); + } + + &_color_danger { + background-color: var(--g-color-base-danger-medium); + } + + &_color_utility { + background-color: var(--g-color-base-utility-medium); + } + + &_color_misc { + background-color: var(--g-color-base-misc-medium); + } + + &_color_neutral { + background-color: var(--g-color-base-neutral-medium); + } + } +} diff --git a/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/components/CellBgPalette/CellBgPalette.tsx b/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/components/CellBgPalette/CellBgPalette.tsx new file mode 100644 index 000000000..d426e401a --- /dev/null +++ b/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/components/CellBgPalette/CellBgPalette.tsx @@ -0,0 +1,58 @@ +import {useMemo} from 'react'; + +import {Palette, type PaletteOption} from '@gravity-ui/uikit'; + +import {cn} from 'src/classname'; +import {i18n} from 'src/i18n/yfm-table'; + +import {CELL_BG_COLORS} from './colors'; + +import './CellBgPalette.scss'; + +const b = cn('yfm-table-cell-bg-palette'); + +const NO_COLOR_VALUE = ''; + +export type CellBgPaletteProps = { + value?: string | null; + onSelect: (color: string | null) => void; +}; + +export const CellBgPalette: React.FC = function YfmTableCellBgPalette({ + // value, + onSelect, +}) { + const options = useMemo( + () => [ + { + value: NO_COLOR_VALUE, + content: , + title: i18n('cells.bg.none'), + }, + ...CELL_BG_COLORS.map((color) => ({ + value: color, + content: , + title: i18n(`cells.bg.${color}`), + })), + ], + [], + ); + + // const paletteValue = useMemo(() => (value ? [value] : [NO_COLOR_VALUE]), [value]); + + const handleUpdate = (values: string[]) => { + const selected = values[0] ?? NO_COLOR_VALUE; + onSelect(selected === NO_COLOR_VALUE ? null : selected); + }; + + return ( + + ); +}; diff --git a/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/components/CellBgPalette/colors.ts b/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/components/CellBgPalette/colors.ts new file mode 100644 index 000000000..dbaafc1fb --- /dev/null +++ b/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/components/CellBgPalette/colors.ts @@ -0,0 +1,11 @@ +export const CELL_BG_COLORS = [ + 'info', + 'positive', + 'warning', + 'danger', + 'utility', + 'misc', + 'neutral', +] as const; + +export type CellBgColor = (typeof CELL_BG_COLORS)[number]; diff --git a/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/components/FloatingMenuControl/FloatingMenuControl.tsx b/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/components/FloatingMenuControl/FloatingMenuControl.tsx index b549f4c94..bec0105ef 100644 --- a/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/components/FloatingMenuControl/FloatingMenuControl.tsx +++ b/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/components/FloatingMenuControl/FloatingMenuControl.tsx @@ -7,6 +7,7 @@ import { ArrowRight, ArrowUp, BroomMotion as ClearCells, + Palette as ColorPalette, TrashBin, Xmark, } from '@gravity-ui/icons'; @@ -15,6 +16,7 @@ import {Icon} from '@gravity-ui/uikit'; import {i18n} from 'src/i18n/yfm-table'; import type {DnDControlHandler} from '../../dnd/dnd'; +import {CellBgPalette} from '../CellBgPalette/CellBgPalette'; import {FloatingMenu, type FloatingMenuProps} from '../FloatingMenu/FloatingMenu'; type ControlType = FloatingMenuProps['dirtype']; @@ -31,6 +33,9 @@ export type FloatingMenuControlProps = { onInsertAfterClick: () => void; onRemoveRangeClick: () => void; onRemoveTableClick: () => void; + cellBackgroundEnabled?: boolean; + currentCellBg?: string | null; + onCellBgChange?: (color: string | null) => void; }; export const FloatingMenuControl: React.FC = @@ -46,6 +51,9 @@ export const FloatingMenuControl: React.FC = onInsertAfterClick, onRemoveRangeClick, onRemoveTableClick, + cellBackgroundEnabled, + currentCellBg, + onCellBgChange, }) { const dropdownItems = useMemo( () => @@ -64,6 +72,28 @@ export const FloatingMenuControl: React.FC = iconStart: , }, ], + ...(cellBackgroundEnabled && onCellBgChange + ? [ + [ + { + text: i18n('cells.bg'), + qa: `g-md-yfm-table-${type}-cell-bg`, + iconStart: , + items: [ + { + text: ( + + ), + action: () => {}, + }, + ], + }, + ], + ] + : []), [ { text: i18n('cells.clear'), @@ -91,6 +121,9 @@ export const FloatingMenuControl: React.FC = [ type, multiple, + cellBackgroundEnabled, + currentCellBg, + onCellBgChange, onClearCellsClick, onInsertAfterClick, onInsertBeforeClick, diff --git a/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/dnd/dnd.scss b/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/dnd/dnd.scss index 86a546da9..ef529670b 100644 --- a/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/dnd/dnd.scss +++ b/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/dnd/dnd.scss @@ -51,7 +51,10 @@ overflow: unset; border-color: var(--g-color-line-brand); - background-color: var(--g-color-base-selection); + + &:not([class^='cell-bg-']) { + background-color: var(--g-color-base-selection); + } &::after { position: absolute; diff --git a/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/index.ts b/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/index.ts index b9738f3f8..69890783c 100644 --- a/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/index.ts +++ b/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/index.ts @@ -3,7 +3,8 @@ import type {ExtensionDeps} from '#core'; import {yfmTableDndPlugin} from './plugins/dnd-plugin'; import {yfmTableFocusPlugin} from './plugins/focus-plugin'; -export const yfmTableControlsPlugins = (opts: {dndEnabled: boolean}) => (_deps: ExtensionDeps) => [ - yfmTableFocusPlugin(opts), - yfmTableDndPlugin(), -]; +export const yfmTableControlsPlugins = + (opts: {dndEnabled: boolean; cellBackground: boolean}) => (_deps: ExtensionDeps) => [ + yfmTableFocusPlugin(opts), + yfmTableDndPlugin(), + ]; diff --git a/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/nodeviews/yfm-table-cell-view.tsx b/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/nodeviews/yfm-table-cell-view.tsx index c8f97fa25..f261756f8 100644 --- a/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/nodeviews/yfm-table-cell-view.tsx +++ b/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/nodeviews/yfm-table-cell-view.tsx @@ -21,6 +21,7 @@ import {insertEmptyColumn} from '../commands/insert-empty-column'; import {insertEmptyRow} from '../commands/insert-empty-row'; import {removeColumnRange} from '../commands/remove-column-range'; import {removeRowRange} from '../commands/remove-row-range'; +import {setCellBg} from '../commands/set-cell-bg'; import {FloatingMenuControl} from '../components/FloatingMenuControl'; import { YfmTableDecorationType as DecoType, @@ -44,6 +45,7 @@ const dropCursorParams: DropCursorParams = { type GetPos = () => number | undefined; type YfmTableCellViewOptions = { dndEnabled: boolean; + cellBackground: boolean; }; export const yfmTableCellView = @@ -62,6 +64,7 @@ class YfmTableCellView implements NodeView { private readonly _renderer; private readonly _logger: Logger2.ILogger; private readonly _dndEnabled: boolean; + private readonly _cellBackground: boolean; private _decoRowUniqKey: number | null = null; private _decoColumnUniqKey: number | null = null; @@ -91,6 +94,7 @@ class YfmTableCellView implements NodeView { node: 'yfm-table', }); this._dndEnabled = opts.dndEnabled; + this._cellBackground = opts.cellBackground; this.dom = document.createElement('td'); this._updateDom(); @@ -109,6 +113,8 @@ class YfmTableCellView implements NodeView { const tableElem = this._view.domAtPos(tablePos + 1).node as Element; + const currentCellBg = this._node.attrs[YfmTableAttr.CellBg] ?? null; + return ( {showRowControl && ( @@ -124,6 +130,9 @@ class YfmTableCellView implements NodeView { onInsertAfterClick={this._onRowInsertAfterClick} onRemoveRangeClick={this._onRowRemoveRangeClick} onRemoveTableClick={this._onRemoveTableClick} + cellBackgroundEnabled={this._cellBackground} + currentCellBg={currentCellBg} + onCellBgChange={this._onRowSetCellBg} /> )} {showColumnControl && ( @@ -139,6 +148,9 @@ class YfmTableCellView implements NodeView { onInsertAfterClick={this._onColumnInsertAfterClick} onRemoveRangeClick={this._onColumnRemoveRangeClick} onRemoveTableClick={this._onRemoveTableClick} + cellBackgroundEnabled={this._cellBackground} + currentCellBg={currentCellBg} + onCellBgChange={this._onColumnSetCellBg} /> )} @@ -214,6 +226,10 @@ class YfmTableCellView implements NodeView { this.dom.classList.remove(prev.attrs[YfmTableAttr.CellAlign]); } + if (prev?.attrs[YfmTableAttr.CellBg]) { + this.dom.classList.remove(`cell-bg-${prev.attrs[YfmTableAttr.CellBg]}`); + } + if (this._node.attrs[YfmTableAttr.Colspan]) this.dom.setAttribute('colspan', this._node.attrs[YfmTableAttr.Colspan]); else this.dom.removeAttribute('colspan'); @@ -228,6 +244,13 @@ class YfmTableCellView implements NodeView { } else { this.dom.removeAttribute(YfmTableAttr.CellAlign); } + + if (this._node.attrs[YfmTableAttr.CellBg] && this._cellBackground) { + this.dom.classList.add(`cell-bg-${this._node.attrs[YfmTableAttr.CellBg]}`); + this.dom.setAttribute(YfmTableAttr.CellBg, this._node.attrs[YfmTableAttr.CellBg]); + } else { + this.dom.removeAttribute(YfmTableAttr.CellBg); + } } private _onRowControlOpenToggle = (open: boolean) => { @@ -308,6 +331,38 @@ class YfmTableCellView implements NodeView { this._view.focus(); }; + private _onRowSetCellBg = (bg: string | null) => { + this._logger.event({event: 'row-set-cell-bg', source: 'row-menu'}); + + const info = this._getCellInfo(); + if (info) { + const rowRange = info.tableDesc.base.getRowRangeByRowIdx(info.cell.row); + setCellBg({ + tablePos: info.table.pos, + rows: iterate(rowRange.startIdx, rowRange.endIdx + 1), + bg, + })(this._view.state, this._view.dispatch); + } + + this._view.focus(); + }; + + private _onColumnSetCellBg = (bg: string | null) => { + this._logger.event({event: 'column-set-cell-bg', source: 'column-menu'}); + + const info = this._getCellInfo(); + if (info) { + const colRange = info.tableDesc.base.getColumnRangeByColumnIdx(info.cell.column); + setCellBg({ + tablePos: info.table.pos, + cols: iterate(colRange.startIdx, colRange.endIdx + 1), + bg, + })(this._view.state, this._view.dispatch); + } + + this._view.focus(); + }; + private _onRowInsertBeforeClick = () => { this._logger.event({event: 'row-insert-before', source: 'row-menu'}); diff --git a/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/plugins/focus-plugin.ts b/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/plugins/focus-plugin.ts index c921f6774..2baeb90cc 100644 --- a/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/plugins/focus-plugin.ts +++ b/packages/editor/src/extensions/yfm/YfmTable/plugins/YfmTableControls/plugins/focus-plugin.ts @@ -31,7 +31,7 @@ function shouldUpdateState(prev: HoverState, curr: HoverState): boolean { return true; } -export const yfmTableFocusPlugin = (opts: {dndEnabled: boolean}) => { +export const yfmTableFocusPlugin = (opts: {dndEnabled: boolean; cellBackground: boolean}) => { return new Plugin({ key: pluginKey, state: { diff --git a/packages/editor/src/i18n/yfm-table/en.json b/packages/editor/src/i18n/yfm-table/en.json index 74457d6d4..4a61d7d79 100644 --- a/packages/editor/src/i18n/yfm-table/en.json +++ b/packages/editor/src/i18n/yfm-table/en.json @@ -8,6 +8,15 @@ "row.remove": "Remove row", "row.remove.multiple": "Remove rows", "cells.clear": "Clear cells", + "cells.bg": "Cell background", + "cells.bg.none": "No color", + "cells.bg.info": "Info", + "cells.bg.positive": "Positive", + "cells.bg.warning": "Warning", + "cells.bg.danger": "Danger", + "cells.bg.utility": "Utility", + "cells.bg.misc": "Misc", + "cells.bg.neutral": "Neutral", "table.remove": "Remove table", "table.menu.cell.align.left": "Align cell content to the left", "table.menu.cell.align.right": "Align cell content to the right", diff --git a/packages/editor/src/i18n/yfm-table/ru.json b/packages/editor/src/i18n/yfm-table/ru.json index 48c60b680..c869d2b68 100644 --- a/packages/editor/src/i18n/yfm-table/ru.json +++ b/packages/editor/src/i18n/yfm-table/ru.json @@ -8,6 +8,15 @@ "row.remove": "Удалить строку", "row.remove.multiple": "Удалить строки", "cells.clear": "Очистить ячейки", + "cells.bg": "Фон ячейки", + "cells.bg.none": "Без цвета", + "cells.bg.info": "Информационный", + "cells.bg.positive": "Позитивный", + "cells.bg.warning": "Предупреждение", + "cells.bg.danger": "Опасность", + "cells.bg.utility": "Утилитарный", + "cells.bg.misc": "Прочий", + "cells.bg.neutral": "Нейтральный", "table.remove": "Удалить таблицу", "table.menu.cell.align.left": "Выровнять контент ячейки по левому краю", "table.menu.cell.align.right": "Выровнять контент ячейки по правому краю", diff --git a/packages/editor/src/styles/markdown.scss b/packages/editor/src/styles/markdown.scss index a8c5fbc95..5dc2e2e56 100644 --- a/packages/editor/src/styles/markdown.scss +++ b/packages/editor/src/styles/markdown.scss @@ -1,4 +1,5 @@ @use '../extensions/yfm/Color/colors'; @use './yc-file.scss'; +@use './yc-table.scss'; @use './yc-colors.scss'; @use './yfm-overrides.scss'; diff --git a/packages/editor/src/styles/yc-table.scss b/packages/editor/src/styles/yc-table.scss new file mode 100644 index 000000000..6ab685305 --- /dev/null +++ b/packages/editor/src/styles/yc-table.scss @@ -0,0 +1,23 @@ +.yfm table td { + &.cell-bg-info { + background-color: var(--g-color-base-info-medium); + } + &.cell-bg-positive { + background-color: var(--g-color-base-positive-medium); + } + &.cell-bg-warning { + background-color: var(--g-color-base-warning-medium); + } + &.cell-bg-danger { + background-color: var(--g-color-base-danger-medium); + } + &.cell-bg-utility { + background-color: var(--g-color-base-utility-medium); + } + &.cell-bg-misc { + background-color: var(--g-color-base-misc-medium); + } + &.cell-bg-neutral { + background-color: var(--g-color-base-neutral-medium); + } +} From 5007207d0603783d751ef437bdeb8c84660a8c71 Mon Sep 17 00:00:00 2001 From: Yuriy Demidov Date: Wed, 6 May 2026 12:38:29 +0300 Subject: [PATCH 2/2] tmp: change content in demo --- demo/src/defaults/content.ts | 191 ----------------------------------- 1 file changed, 191 deletions(-) diff --git a/demo/src/defaults/content.ts b/demo/src/defaults/content.ts index 1c7011d28..47d5fd849 100644 --- a/demo/src/defaults/content.ts +++ b/demo/src/defaults/content.ts @@ -1,169 +1,6 @@ export const markup = `   -Welcome to the editor! Start typing the character \`/\` - -![Markdown Editor](https://github.com/user-attachments/assets/0b4e5f65-54cf-475f-9c68-557a4e9edb46 =700x) - -## Markdown WYSIWYG and markup editor - -MarkdownEditor is a powerful tool for working with Markdown, which combines WYSIWYG and Markup modes. This means that you can create and edit content in a convenient visual mode, as well as have full control over the markup. - -The editor supports following formats: - -* WYSIWYG - -* markup - -Click on the gear in the upper right corner to change the mode and see the \`md\` markup. - -### Various blocks included - -{% cut "Combine different blocks" %} - -{% note info "Block for notes, tips, warnings, and alerts" %} - -Depending on the content, notes with different titles and formats are used: - -* Note: provides additional information. -* Tip: offers a recommendation. -* Warning: issues a warning. -* Alert: indicates a restriction. - -{% endnote %} - -> [Improve](https://github.com/gravity-ui/markdown-editor/blob/main/docs/how-to-add-preview.md) the editor interface -> -> *improved by you* - -{% endcut %} - -Or write your extension using a [convenient api](https://github.com/gravity-ui/markdown-editor/blob/main/docs/how-to-create-extension.md) - -### A user-friendly API is provided - -Easily connect to your React app with a hook: - -\`\`\`plaintext -import React from 'react'; -import { useMarkdownEditor, MarkdownEditorView } from '@gravity-ui/markdown-editor'; -import { toaster } from '@gravity-ui/uikit/toaster-singleton'; - -function Editor({ onSubmit }) { - const editor = useMarkdownEditor({ allowHTML: false }); - - React.useEffect(() => { - function submitHandler() { - // Serialize current content to markdown markup - const value = editor.getValue(); - onSubmit(value); - } - - editor.on('submit', submitHandler); - return () => { - editor.off('submit', submitHandler); - }; - }, [onSubmit]); - - return ; -} -\`\`\` - -### Convenient UX control is equipped - -#### Hot keys - -{% list tabs %} - -- WYSIWYG mode - - - - |Formatting|Windows Shortcut|Mac OS Shortcut| - |:---|:---|:---| - |Bold text|Ctrl \\+ B|⌘ \\+ B| - |Italic|Ctrl \\+ I|⌘ \\+ I| - |Underlined text|Ctrl \\+ U|⌘ \\+ U| - |Strikethrough text|Ctrl \\+ Shift \\+ S|⌘ \\+ Shift \\+ S| - -- Markup mode - - - - |Formatting|Markup|Result| - |:---|:---|:---| - |Bold text|\`**Bold**\`|**Bold**| - |Italic|\`*Italic*\`|*Italic*| - |Underlined text|\`++Underlined++\`|++Underlined++| - |Strikethrough text|\`~~Strikethrough~~\`|~~Strikethrough~~| - -{% endlist %} - -#### Context menu - -Select this text and you will see **a context menu**. - -#### Auto-conversion - -Quickly create blocks by entering characters that will be replaced by blocks. For example, the automatic conversion of \`-\` and space creates a list, \`>\` and space creates a quote. Try it out. - ---- - -### Current and future features - -[X] Some already finished things - -[ ] VS Code plugin - -[ ] Mobile version - -### And a multitude of other functionalities :sweat_smile: :fire: - -See - -# More examples {#anchor} - -{% cut "Headings" %} - -# Heading 1 - -## Heading 2 - -### Heading 3 - -#### Heading 4 - -##### Heading 5 - -###### Heading 6 - -{% endcut %} - -{% cut "This is a cut heading" %} - -**A** *here* ~~it~~ ++is awesome++ ^c^~o~^n^~t~^e^~n~^t^ - -> Done deal - deal done \`(quote)\` - -{% endcut %} - -{% cut "Formulas" %} - -This is an inline formula: $\\sqrt{3x-1}+(1+x)^2$ - -And here is a block formula: - -$$f(\\relax{x}) = \\int_{-\\infty}^\\infty - \\hat f(\\xi)\\,e^{2 \\pi i \\xi x} - \\,d\\xi -$$ - -*Click on the formula to edit it* - -{% endcut %} - ---- - #| || @@ -247,34 +84,6 @@ New approaches to learning in the digital age || |# ---- - -{% note info "Attention, please!" %} - -* Thank - - 1. you - - 2. for - - 1. your - - 3. attention - -* (nested lists) - -> > > Quotes -> > -> > Nested -> -> As well - -And ##monospace## can be **##com##**##bined\\*## - -{% endnote %} - ---- - `.trim(); export const loremIpsum = `