Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,16 +1,40 @@
import { UmbBubbleMenuPlugin } from './tiptap-umb-bubble-menu.extension.js';
import { CellSelection, TableMap } from '@tiptap/pm/tables';
import { CellSelection, TableMap, TableView } from '@tiptap/pm/tables';
import { Decoration, DecorationSet, EditorView } from '@tiptap/pm/view';
import { EditorState, Plugin, Selection, Transaction } from '@tiptap/pm/state';
import { findParentNode, Editor } from '@tiptap/core';
import { Node as PMNode, ResolvedPos } from '@tiptap/pm/model';
import { Node as ProseMirrorNode, ResolvedPos } from '@tiptap/pm/model';
import { Table } from '@tiptap/extension-table';
import { TableCell } from '@tiptap/extension-table-cell';
import { TableHeader } from '@tiptap/extension-table-header';
import { TableRow } from '@tiptap/extension-table-row';
import type { Rect } from '@tiptap/pm/tables';

export const UmbTable = Table.configure({ resizable: true });
// NOTE: Custom TableView, to allow for custom styles to be applied to the <table> element. [LK]
// ref: https://github.com/ueberdosis/tiptap/blob/v2.11.5/packages/extension-table/src/TableView.ts
export class UmbTableView extends TableView {
constructor(node: ProseMirrorNode, cellMinWidth: number) {
super(node, cellMinWidth);
this.#updateTableStyle(node);
}

override update(node: ProseMirrorNode): boolean {
if (!super.update(node)) return false;
this.#updateTableStyle(node);
return true;
}

#updateTableStyle(node: ProseMirrorNode) {
if (node.attrs.style) {
// NOTE: The `min-width` inline style is handled by the Tiptap TableView, so we need to preserve it. [LK]
const minWidth = this.table.style.minWidth;
const styles = node.attrs.style as string;
this.table.style.cssText = `${styles}; min-width: ${minWidth};`;
}
}
}

export const UmbTable = Table.configure({ resizable: true, View: UmbTableView });

export const UmbTableRow = TableRow.extend({
allowGapCursor: false,
Expand Down Expand Up @@ -284,7 +308,7 @@ const getCellsInColumn = (columnIndex: number | number[]) => (selection: Selecti

return acc;
},
[] as { pos: number; start: number; node: PMNode | null | undefined }[],
[] as { pos: number; start: number; node: ProseMirrorNode | null | undefined }[],
);
}
return null;
Expand Down Expand Up @@ -318,7 +342,7 @@ const getCellsInRow = (rowIndex: number | number[]) => (selection: Selection) =>

return acc;
},
[] as { pos: number; start: number; node: PMNode | null | undefined }[],
[] as { pos: number; start: number; node: ProseMirrorNode | null | undefined }[],
);
}

Expand Down Expand Up @@ -348,7 +372,7 @@ const getCellsInTable = (selection: Selection) => {
return null;
};

const findParentNodeClosestToPos = ($pos: ResolvedPos, predicate: (node: PMNode) => boolean) => {
const findParentNodeClosestToPos = ($pos: ResolvedPos, predicate: (node: ProseMirrorNode) => boolean) => {
for (let i = $pos.depth; i > 0; i -= 1) {
const node = $pos.node(i);

Expand All @@ -366,7 +390,7 @@ const findParentNodeClosestToPos = ($pos: ResolvedPos, predicate: (node: PMNode)
};

const findCellClosestToPos = ($pos: ResolvedPos) => {
const predicate = (node: PMNode) => node.type.spec.tableRole && /cell/i.test(node.type.spec.tableRole);
const predicate = (node: ProseMirrorNode) => node.type.spec.tableRole && /cell/i.test(node.type.spec.tableRole);

return findParentNodeClosestToPos($pos, predicate);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -919,7 +919,7 @@ export const data: Array<UmbMockDocumentModel> = [
<span>This is a plain old span tag.</span>
<span style="color:red;">Hello <span style="color:blue;">world</span>.</span>
</div>
<table style="width: 100%;">
<table>
<thead>
<tr>
<th>Version</th>
Expand Down
1 change: 1 addition & 0 deletions src/Umbraco.Web.UI.Client/src/packages/tiptap/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './components/anchor-modal/constants.js';
export * from './components/character-map/constants.js';
export * from './extensions/table/components/constants.js';
export * from './property-editors/tiptap/constants.js';
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './base.js';
export * from './table/index.js';
export type * from './types.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const UMB_TIPTAP_TABLE_PROPERTIES_MODAL_ALIAS = 'Umb.Modal.Tiptap.TableProperties';
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class UmbTiptapTableInsertElement extends UmbLitElement {
this._selectedColumn = column;
this._selectedRow = row;

this.editor?.chain().focus().insertTable({ rows: row, cols: column }).run();
this.editor?.chain().focus().insertTable({ rows: row, cols: column, withHeaderRow: false }).run();
}

#onMouseover(column: number, row: number) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import type {
UmbTiptapTablePropertiesModalData,
UmbTiptapTablePropertiesModalValue,
} from './table-properties-modal.token.js';
import { css, customElement, html, repeat, when } from '@umbraco-cms/backoffice/external/lit';
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
import type { UmbPropertyDatasetElement, UmbPropertyValueData } from '@umbraco-cms/backoffice/property';
import type { UmbPropertyEditorConfig } from '@umbraco-cms/backoffice/property-editor';

type UmbProperty = {
alias: string;
config?: UmbPropertyEditorConfig;
description?: string;
label: string;
propertyEditorUiAlias: string;
};

@customElement('umb-tiptap-table-properties-modal')
export class UmbTiptapTablePropertiesModalElement extends UmbModalBaseElement<
UmbTiptapTablePropertiesModalData,
UmbTiptapTablePropertiesModalValue
> {
#appearance = { labelOnTop: true };

#properties: Array<UmbProperty> = [
{ alias: 'width', label: 'Width', propertyEditorUiAlias: 'Umb.PropertyEditorUi.TextBox' },
{ alias: 'height', label: 'Height', propertyEditorUiAlias: 'Umb.PropertyEditorUi.TextBox' },
{
alias: 'backgroundColor',
label: 'Background color',
propertyEditorUiAlias: 'Umb.PropertyEditorUi.EyeDropper',
config: [{ alias: 'showPalette', value: true }],
},
{
alias: 'alignment',
label: 'Alignment',
propertyEditorUiAlias: 'Umb.PropertyEditorUi.Dropdown',
config: [
{
alias: 'items',
value: [
{ name: 'None', value: 'none' },
{ name: 'Left', value: 'left' },
{ name: 'Center', value: 'center' },
{ name: 'Right', value: 'right' },
],
},
],
},
{ alias: 'borderWidth', label: 'Border width', propertyEditorUiAlias: 'Umb.PropertyEditorUi.TextBox' },
{
alias: 'borderStyle',
label: 'Border style',
propertyEditorUiAlias: 'Umb.PropertyEditorUi.Dropdown',
config: [
{
alias: 'items',
value: [
{ name: 'Solid', value: 'solid' },
{ name: 'Dotted', value: 'dotted' },
{ name: 'Dashed', value: 'dashed' },
{ name: 'Double', value: 'double' },
{ name: 'Groove', value: 'groove' },
{ name: 'Ridge', value: 'ridge' },
{ name: 'Inset', value: 'inset' },
{ name: 'Outset', value: 'outset' },
{ name: 'None', value: 'none' },
{ name: 'Hidden', value: 'hidden' },
],
},
],
},
{
alias: 'borderColor',
label: 'Border color',
propertyEditorUiAlias: 'Umb.PropertyEditorUi.EyeDropper',
config: [{ alias: 'showPalette', value: true }],
},
];

#values: Array<UmbPropertyValueData> = [];

override connectedCallback(): void {
super.connectedCallback();

if (this.data) {
this.#values = Object.entries(this.data).map(([alias, value]) => ({ alias, value }));
}
}

#onChange(event: Event & { target: UmbPropertyDatasetElement }) {
this.#values = event.target.value;
}

#onSubmit() {
this.value = this.#values;
this._submitModal();
}

override render() {
return html`
<umb-body-layout headline="Table properties">
<uui-box>
${when(
this.#properties?.length,
() => html`
<umb-property-dataset .value=${this.#values} @change=${this.#onChange}>
${repeat(
this.#properties,
(property) => property.alias,
(property) => html`
<umb-property
alias=${property.alias}
label=${property.label}
property-editor-ui-alias=${property.propertyEditorUiAlias}
.appearance=${this.#appearance}
.config=${property.config}>
</umb-property>
`,
)}
</umb-property-dataset>
`,
() => html`<p>There are no properties for this modal.</p>`,
)}
</uui-box>
<uui-button
slot="actions"
label=${this.localize.term('general_cancel')}
@click=${this._rejectModal}></uui-button>
<uui-button
slot="actions"
color="positive"
look="primary"
label=${this.localize.term('bulk_done')}
@click=${this.#onSubmit}></uui-button>
</umb-body-layout>
`;
}

static override styles = [
css`
umb-property {
--uui-size-layout-1: var(--uui-size-space-4);
}
`,
];
}

export { UmbTiptapTablePropertiesModalElement as element };

declare global {
interface HTMLElementTagNameMap {
'umb-tiptap-table-properties-modal': UmbTiptapTablePropertiesModalElement;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { UMB_TIPTAP_TABLE_PROPERTIES_MODAL_ALIAS } from './constants.js';
import { UmbModalToken } from '@umbraco-cms/backoffice/modal';
import type { UmbPropertyValueData } from '@umbraco-cms/backoffice/property';

export type UmbTiptapTablePropertiesModalData = Record<string, unknown>;

export type UmbTiptapTablePropertiesModalValue = Array<UmbPropertyValueData>;

export const UMB_TIPTAP_TABLE_PROPERTIES_MODAL = new UmbModalToken<
UmbTiptapTablePropertiesModalData,
UmbTiptapTablePropertiesModalValue
>(UMB_TIPTAP_TABLE_PROPERTIES_MODAL_ALIAS, {
modal: { size: 'small', type: 'sidebar' },
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './components/table-properties-modal.token.js';
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import type { ManifestTiptapExtension } from '../types.js';
import { UMB_TIPTAP_TABLE_PROPERTIES_MODAL_ALIAS } from './components/constants.js';

const coreExtensions: Array<ManifestTiptapExtension> = [
const modals: Array<UmbExtensionManifest> = [
{
type: 'modal',
alias: UMB_TIPTAP_TABLE_PROPERTIES_MODAL_ALIAS,
name: 'Tiptap Table Properties Modal',
element: () => import('./components/table-properties-modal.element.js'),
},
];

const coreExtensions: Array<UmbExtensionManifest> = [
{
type: 'tiptapExtension',
kind: 'button',
Expand Down Expand Up @@ -58,10 +67,11 @@ const toolbarExtensions: Array<UmbExtensionManifest> = [
],
separatorAfter: true,
},
{ label: 'Table properties', data: 'tableProperties' },
{ label: 'Delete table', icon: 'icon-trash', data: 'deleteTable' },
],
},
},
];

export const manifests = [...coreExtensions, ...toolbarExtensions];
export const manifests = [...modals, ...coreExtensions, ...toolbarExtensions];
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export default class UmbTiptapTableExtensionApi extends UmbTiptapExtensionApiBas
border-radius: 0.25rem;
border-spacing: 0;
box-sizing: border-box;
width: 100%;
max-width: 100%;

td,
Expand Down Expand Up @@ -45,7 +44,6 @@ export default class UmbTiptapTableExtensionApi extends UmbTiptapExtensionApiBas
}

th {
background-color: var(--uui-color-background);
font-weight: bold;
}

Expand All @@ -71,7 +69,7 @@ export default class UmbTiptapTableExtensionApi extends UmbTiptapExtensionApiBas
}

.selectedCell {
background-color: var(--uui-color-surface-emphasis);
background-color: color-mix(in srgb, var(--uui-color-surface-emphasis) 50%, transparent);
border-color: var(--uui-color-selected);
}

Expand Down
Loading
Loading