diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 24c064470c..d1233645ab 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -32,6 +32,10 @@ "@tiptap/extension-ordered-list": "^2.7.0", "@tiptap/extension-paragraph": "^2.7.0", "@tiptap/extension-strike": "^2.7.0", + "@tiptap/extension-table": "^2.7.3", + "@tiptap/extension-table-cell": "^2.7.3", + "@tiptap/extension-table-header": "^2.7.3", + "@tiptap/extension-table-row": "^2.7.3", "@tiptap/extension-text": "^2.7.0", "@tiptap/extension-text-align": "^2.6.6", "@tiptap/extension-underline": "^2.6.6", @@ -6731,6 +6735,55 @@ "@tiptap/core": "^2.7.0-pre.0" } }, + "node_modules/@tiptap/extension-table": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-table/-/extension-table-2.7.3.tgz", + "integrity": "sha512-zv1SGgVywTY3vs+9EIMdYS7jZMovlfsraZ3Qdz1YkqN3dNZBUukXrfpZaJqzVwUvRehCVvjA+HG7zH12RU/XYQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-table-cell": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-table-cell/-/extension-table-cell-2.7.3.tgz", + "integrity": "sha512-C6f2dAcatk/XROZ2Q1owv4DBrTyfVzfsK1Jh7rk3mkpEa8oh/lPKR8thYjmaLC/BlPYjtVuIbMIqp9lz6U/Ufw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-table-header": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-table-header/-/extension-table-header-2.7.3.tgz", + "integrity": "sha512-eL1FVn+GBf0dRYmsE88QeJa3azwVKhyYDAFTmoGIwilHsjbNzb4ptUGi+ko2XpxLHvY+XfGLe3+UEZbQ3FDOIA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-table-row": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-table-row/-/extension-table-row-2.7.3.tgz", + "integrity": "sha512-gB6gXYVCGWn6IDb/oV3ds1LI0yLLIwymcvcu1MWnT9p8qClZPaId/J6/+mQbSGCEc8G1SzYYUhnu2dsaVIsFsw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, "node_modules/@tiptap/extension-text": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.7.0.tgz", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index d058a21df1..d50b98524f 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -212,6 +212,10 @@ "@tiptap/extension-ordered-list": "^2.7.0", "@tiptap/extension-paragraph": "^2.7.0", "@tiptap/extension-strike": "^2.7.0", + "@tiptap/extension-table": "^2.7.3", + "@tiptap/extension-table-cell": "^2.7.3", + "@tiptap/extension-table-header": "^2.7.3", + "@tiptap/extension-table-row": "^2.7.3", "@tiptap/extension-text": "^2.7.0", "@tiptap/extension-text-align": "^2.6.6", "@tiptap/extension-underline": "^2.6.6", diff --git a/src/Umbraco.Web.UI.Client/src/external/tiptap/extensions/tiptap-figcaption.extension.ts b/src/Umbraco.Web.UI.Client/src/external/tiptap/extensions/tiptap-figcaption.extension.ts new file mode 100644 index 0000000000..23b188b7bf --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/tiptap/extensions/tiptap-figcaption.extension.ts @@ -0,0 +1,40 @@ +import { Node } from '@tiptap/core'; + +export interface FigcaptionOptions { + /** + * HTML attributes to add to the image element. + * @default {} + * @example { class: 'foo' } + */ + HTMLAttributes: Record; +} + +export const Figcaption = Node.create({ + name: 'figcaption', + + addOptions() { + return { + HTMLAttributes: {}, + }; + }, + + group: 'block', + + content: 'inline*', + + selectable: false, + + draggable: false, + + parseHTML() { + return [ + { + tag: 'figcaption', + }, + ]; + }, + + renderHTML({ HTMLAttributes }) { + return [this.name, HTMLAttributes, 0]; + }, +}); diff --git a/src/Umbraco.Web.UI.Client/src/external/tiptap/extensions/tiptap-figure.extension.ts b/src/Umbraco.Web.UI.Client/src/external/tiptap/extensions/tiptap-figure.extension.ts new file mode 100644 index 0000000000..0cae9f7b47 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/tiptap/extensions/tiptap-figure.extension.ts @@ -0,0 +1,52 @@ +import { mergeAttributes, Node } from '@tiptap/core'; + +export interface FigureOptions { + /** + * HTML attributes to add to the image element. + * @default {} + * @example { class: 'foo' } + */ + HTMLAttributes: Record; +} + +export const Figure = Node.create({ + name: 'figure', + group: 'block', + content: 'block+', + draggable: true, + selectable: true, + isolating: true, + atom: true, + + addAttributes() { + return { + figcaption: { + default: '', + }, + }; + }, + + addOptions() { + return { + HTMLAttributes: {}, + }; + }, + + parseHTML() { + return [ + { + tag: 'figure', + getAttrs: (dom) => { + const figcaption = dom.querySelector('figcaption'); + return { + figcaption: figcaption?.textContent || '', + }; + }, + }, + ]; + }, + + renderHTML({ HTMLAttributes }) { + return [this.name, mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]; + }, +}); diff --git a/src/Umbraco.Web.UI.Client/src/external/tiptap/extensions/tiptap-umb-image.extension.ts b/src/Umbraco.Web.UI.Client/src/external/tiptap/extensions/tiptap-umb-image.extension.ts index fb3d645b4e..81807bc9ad 100644 --- a/src/Umbraco.Web.UI.Client/src/external/tiptap/extensions/tiptap-umb-image.extension.ts +++ b/src/Umbraco.Web.UI.Client/src/external/tiptap/extensions/tiptap-umb-image.extension.ts @@ -1,7 +1,19 @@ import Image from '@tiptap/extension-image'; +export interface UmbImageAttributes { + src: string; + alt?: string; + title?: string; + width?: string; + height?: string; + loading?: string; + srcset?: string; + sizes?: string; + 'data-tmpimg'?: string; + 'data-udi'?: string; +} + export const UmbImage = Image.extend({ - name: 'umbImage', addAttributes() { return { ...this.parent?.(), @@ -22,7 +34,6 @@ export const UmbImage = Image.extend({ }, 'data-tmpimg': { default: null }, 'data-udi': { default: null }, - 'data-caption': { default: null }, }; }, }); @@ -38,19 +49,7 @@ declare module '@tiptap/core' { * .commands * .setImage({ src: 'https://tiptap.dev/logo.png', alt: 'tiptap', title: 'tiptap logo' }) */ - setImage: (options: { - src: string; - alt?: string; - title?: string; - width?: string; - height?: string; - loading?: string; - srcset?: string; - sizes?: string; - 'data-tmpimg'?: string; - 'data-udi'?: string; - 'data-caption'?: string; - }) => ReturnType; + setImage: (options: UmbImageAttributes) => ReturnType; }; } } diff --git a/src/Umbraco.Web.UI.Client/src/external/tiptap/index.ts b/src/Umbraco.Web.UI.Client/src/external/tiptap/index.ts index 6f9245ee78..92b7ceddbb 100644 --- a/src/Umbraco.Web.UI.Client/src/external/tiptap/index.ts +++ b/src/Umbraco.Web.UI.Client/src/external/tiptap/index.ts @@ -1,4 +1,4 @@ -// REQUIRED EXTENSIONS START +// REQUIRED EXTENSIONS export * from '@tiptap/core'; export { Document } from '@tiptap/extension-document'; export { Dropcursor } from '@tiptap/extension-dropcursor'; @@ -7,7 +7,8 @@ export { HardBreak } from '@tiptap/extension-hard-break'; export { History } from '@tiptap/extension-history'; export { Paragraph } from '@tiptap/extension-paragraph'; export { Text } from '@tiptap/extension-text'; -// REQUIRED EXTENSIONS END + +// OPTIONAL EXTENSIONS export { Blockquote } from '@tiptap/extension-blockquote'; export { Bold } from '@tiptap/extension-bold'; export { BulletList } from '@tiptap/extension-bullet-list'; @@ -15,14 +16,21 @@ export { Code } from '@tiptap/extension-code'; export { CodeBlock } from '@tiptap/extension-code-block'; export { Heading } from '@tiptap/extension-heading'; export { HorizontalRule } from '@tiptap/extension-horizontal-rule'; +export { Image } from '@tiptap/extension-image'; export { Italic } from '@tiptap/extension-italic'; export { Link } from '@tiptap/extension-link'; export { ListItem } from '@tiptap/extension-list-item'; export { OrderedList } from '@tiptap/extension-ordered-list'; export { Strike } from '@tiptap/extension-strike'; +export { Table } from '@tiptap/extension-table'; +export { TableCell } from '@tiptap/extension-table-cell'; +export { TableHeader } from '@tiptap/extension-table-header'; +export { TableRow } from '@tiptap/extension-table-row'; export { TextAlign } from '@tiptap/extension-text-align'; export { Underline } from '@tiptap/extension-underline'; -export { Image } from '@tiptap/extension-image'; + // CUSTOM EXTENSIONS export * from './extensions/tiptap-umb-embedded-media.extension.js'; +export * from './extensions/tiptap-figcaption.extension.js'; +export * from './extensions/tiptap-figure.extension.js'; export * from './extensions/tiptap-umb-image.extension.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json index 4d592bfb10..9c53e18410 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json @@ -1982,6 +1982,10 @@ "name": "icon-tab-key", "file": "arrow-right-to-line.svg" }, + { + "name": "icon-table", + "file": "table.svg" + }, { "name": "icon-tag", "file": "tag.svg" diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons.ts index 3139743a15..1f55c16a7f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons.ts @@ -1867,6 +1867,10 @@ name: "icon-tab-key", path: () => import("./icons/icon-tab-key.js"), },{ +name: "icon-table", + +path: () => import("./icons/icon-table.js"), +},{ name: "icon-tag", path: () => import("./icons/icon-tag.js"), diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-table.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-table.ts new file mode 100644 index 0000000000..283a8a901c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-table.ts @@ -0,0 +1,17 @@ +export default ` + + + + + + +`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-caption-alt-text/media-caption-alt-text-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-caption-alt-text/media-caption-alt-text-modal.element.ts index adb87d2e7d..40581dd0a5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-caption-alt-text/media-caption-alt-text-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-caption-alt-text/media-caption-alt-text-modal.element.ts @@ -27,7 +27,7 @@ export class UmbMediaCaptionAltTextModalElement extends UmbModalBaseElement< const { data } = await this.#mediaDetailRepository.requestByUnique(this.#mediaUnique); if (!data) return; - this.value = { altText: data.variants[0].name, caption: undefined, url: data.urls[0]?.url ?? '' }; + this.value = { ...this.value, altText: this.value.altText ?? data.variants[0].name, url: data.urls[0]?.url ?? '' }; } override render() { @@ -46,11 +46,14 @@ export class UmbMediaCaptionAltTextModalElement extends UmbModalBaseElement< (this.value = { ...this.value, caption: e.target.value as string })}> - ${this.value?.altText - ${this.value?.caption ?? ''} +
+ ${this.value?.altText +
${this.value?.caption ?? ''}
+
@@ -64,7 +67,7 @@ export class UmbMediaCaptionAltTextModalElement extends UmbModalBaseElement< `; } - static override styles = [ + static override readonly styles = [ css` uui-input { margin-bottom: var(--uui-size-layout-1); @@ -74,6 +77,17 @@ export class UmbMediaCaptionAltTextModalElement extends UmbModalBaseElement< display: flex; flex-direction: column; } + + #mainobject { + display: flex; + flex-direction: column; + max-width: 100%; + + img { + max-width: 100%; + height: auto; + } + } `, ]; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/components/input-tiptap/input-tiptap.element.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/components/input-tiptap/input-tiptap.element.ts index df28f6ea32..cdce8380da 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/components/input-tiptap/input-tiptap.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/components/input-tiptap/input-tiptap.element.ts @@ -205,6 +205,65 @@ export class UmbInputTiptapElement extends UmbFormControlMixin * { + margin-bottom: 0; + } + } + + th { + background-color: var(--uui-color-background); + font-weight: bold; + text-align: left; + } + + .selectedCell:after { + background: var(--uui-color-surface-emphasis); + content: ''; + left: 0; + right: 0; + top: 0; + bottom: 0; + pointer-events: none; + position: absolute; + z-index: 2; + } + + .column-resize-handle { + background-color: var(--uui-color-default); + bottom: -2px; + pointer-events: none; + position: absolute; + right: -2px; + top: 0; + width: 3px; + } + } + } + + .resize-cursor { + cursor: ew-resize; + cursor: col-resize; } } `, diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/figure.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/figure.extension.ts new file mode 100644 index 0000000000..1da6bdec35 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/figure.extension.ts @@ -0,0 +1,6 @@ +import { UmbTiptapExtensionApiBase } from '../types.js'; +import { Figure, Figcaption } from '@umbraco-cms/backoffice/external/tiptap'; + +export default class UmbTiptapFigureExtensionApi extends UmbTiptapExtensionApiBase { + getTiptapExtensions = () => [Figcaption, Figure]; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/manifests.ts index 9ef58cd081..309598a14f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/manifests.ts @@ -53,6 +53,18 @@ export const manifests: Array import('./figure.extension.js'), + weight: 955, + meta: { + alias: 'figure', + icon: 'icon-frame', + label: 'Figure', + }, + }, { type: 'tiptapExtension', kind: 'button', @@ -153,6 +165,19 @@ export const manifests: Array import('./table.extension.js'), + weight: 909, + meta: { + alias: 'table', + icon: 'icon-table', + label: 'Table', + }, + }, { type: 'tiptapExtension', kind: 'button', diff --git a/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/table.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/table.extension.ts new file mode 100644 index 0000000000..ae2d71fa73 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/rte/tiptap/extensions/core/table.extension.ts @@ -0,0 +1,11 @@ +import { UmbTiptapToolbarElementApiBase } from '../types.js'; +import { Table, TableHeader, TableRow, TableCell } from '@umbraco-cms/backoffice/external/tiptap'; +import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; + +export default class UmbTiptapTableExtensionApi extends UmbTiptapToolbarElementApiBase { + getTiptapExtensions = () => [Table.configure({ resizable: true }), TableHeader, TableRow, TableCell]; + + override execute(editor?: Editor) { + editor?.commands.insertTable({ rows: 3, cols: 3, withHeaderRow: true }); + } +}