Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ export const Div = Node.create<DivOptions>({
},

parseHTML() {
return [{ tag: 'div' }];
return [{ tag: this.name }];
},

renderHTML({ HTMLAttributes }) {
return ['div', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
return [this.name, mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,48 @@ export const HtmlGlobalAttributes = Extension.create<HtmlGlobalAttributesOptions
},
];
},

addCommands() {
return {
setClassName:
(className, type) =>
({ commands }) => {
if (!className) return false;
const types = type ? [type] : this.options.types;
return types
.map((type) => commands.updateAttributes(type, { class: className }))
.every((response) => response);
},
unsetClassName:
(type) =>
({ commands }) => {
const types = type ? [type] : this.options.types;
return types.map((type) => commands.resetAttributes(type, 'class')).every((response) => response);
},
setId:
(id, type) =>
({ commands }) => {
if (!id) return false;
const types = type ? [type] : this.options.types;
return types.map((type) => commands.updateAttributes(type, { id })).every((response) => response);
},
unsetId:
(type) =>
({ commands }) => {
const types = type ? [type] : this.options.types;
return types.map((type) => commands.resetAttributes(type, 'id')).every((response) => response);
},
};
},
});

declare module '@tiptap/core' {
interface Commands<ReturnType> {
htmlGlobalAttributes: {
setClassName: (className?: string, type?: string) => ReturnType;
unsetClassName: (type?: string) => ReturnType;
setId: (id?: string, type?: string) => ReturnType;
unsetId: (type?: string) => ReturnType;
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const Span = Mark.create<SpanOptions>({
return {
setSpanStyle:
(styles) =>
({ commands, editor, chain }) => {
({ commands, editor }) => {
if (!styles) return false;

const existing = editor.getAttributes(this.name)?.style as string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -986,6 +986,10 @@
"name": "icon-heading-3",
"file": "heading-3.svg"
},
{
"name": "icon-heading-4",
"file": "heading-4.svg"
},
{
"name": "icon-headphones",
"file": "headphones.svg"
Expand Down Expand Up @@ -1507,6 +1511,10 @@
"name": "icon-partly-cloudy",
"file": "cloud-sun.svg"
},
{
"name": "icon-paragraph",
"file": "pilcrow.svg"
},
{
"name": "icon-paste-in",
"file": "clipboard-paste.svg",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,9 @@ path: () => import("./icons/icon-heading-2.js"),
name: "icon-heading-3",
path: () => import("./icons/icon-heading-3.js"),
},{
name: "icon-heading-4",
path: () => import("./icons/icon-heading-4.js"),
},{
name: "icon-headphones",
path: () => import("./icons/icon-headphones.js"),
},{
Expand Down Expand Up @@ -1217,6 +1220,9 @@ path: () => import("./icons/icon-paper-plane.js"),
name: "icon-partly-cloudy",
path: () => import("./icons/icon-partly-cloudy.js"),
},{
name: "icon-paragraph",
path: () => import("./icons/icon-paragraph.js"),
},{
name: "icon-paste-in",
legacy: true,
hidden: true,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default `<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.75" class="lucide lucide-heading-4" viewBox="0 0 24 24"><path d="M12 18V6M17 10v3a1 1 0 0 0 1 1h3M21 10v8M4 12h8M4 18V6"/></svg>`;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default `<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.75" class="lucide lucide-pilcrow" viewBox="0 0 24 24"><path d="M13 4v16M17 4v16M19 4H9.5a4.5 4.5 0 0 0 0 9H13"/></svg>`;
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { css, customElement, html, ifDefined, property, repeat, when } from '@umbraco-cms/backoffice/external/lit';
import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api';
import { UUIPopoverContainerElement } from '@umbraco-cms/backoffice/external/uui';

export type UmbCascadingMenuItem = {
Expand All @@ -12,7 +13,7 @@ export type UmbCascadingMenuItem = {
};

@customElement('umb-cascading-menu-popover')
export class UmbCascadingMenuPopoverElement extends UUIPopoverContainerElement {
export class UmbCascadingMenuPopoverElement extends UmbElementMixin(UUIPopoverContainerElement) {
@property({ type: Array })
items?: Array<UmbCascadingMenuItem>;

Expand Down Expand Up @@ -70,6 +71,8 @@ export class UmbCascadingMenuPopoverElement extends UUIPopoverContainerElement {
element.setAttribute('popovertarget', popoverId);
}

const label = this.localize.string(item.label);

return html`
<div
@mouseenter=${() => this.#onMouseEnter(item, popoverId)}
Expand All @@ -80,11 +83,12 @@ export class UmbCascadingMenuPopoverElement extends UUIPopoverContainerElement {
() => html`
<uui-menu-item
class=${item.separatorAfter ? 'separator' : ''}
label=${label}
popovertarget=${popoverId}
@click-label=${() => this.#onClick(item, popoverId)}>
${when(item.icon, (icon) => html`<uui-icon slot="icon" name=${icon}></uui-icon>`)}
<div slot="label" class="menu-item">
<span style=${ifDefined(item.style)}>${item.label}</span>
<span style=${ifDefined(item.style)}>${label}</span>
${when(item.items, () => html`<uui-symbol-expand></uui-symbol-expand>`)}
</div>
</uui-menu-item>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export class UmbTiptapToolbarElement extends UmbLitElement {
},
undefined,
undefined,
() => import('../toolbar/default-tiptap-toolbar-element.api.js'),
() => import('../toolbar/default-tiptap-toolbar-api.js'),
);

this.#extensionsController.apiProperties = { configuration: this.configuration };
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { UmbTiptapToolbarElementApiBase } from '../../extensions/base.js';
import type { MetaTiptapToolbarStyleMenuItem } from '../../extensions/types.js';
import type { ChainedCommands, Editor } from '@umbraco-cms/backoffice/external/tiptap';

export default class UmbTiptapToolbarStyleMenuApi extends UmbTiptapToolbarElementApiBase {
#commands: Record<string, { type: string; command: (chain: ChainedCommands) => ChainedCommands }> = {
h1: { type: 'heading', command: (chain) => chain.toggleHeading({ level: 1 }) },
h2: { type: 'heading', command: (chain) => chain.toggleHeading({ level: 2 }) },
h3: { type: 'heading', command: (chain) => chain.toggleHeading({ level: 3 }) },
h4: { type: 'heading', command: (chain) => chain.toggleHeading({ level: 4 }) },
h5: { type: 'heading', command: (chain) => chain.toggleHeading({ level: 5 }) },
h6: { type: 'heading', command: (chain) => chain.toggleHeading({ level: 6 }) },
p: { type: 'paragraph', command: (chain) => chain.setParagraph() },
blockquote: { type: 'blockquote', command: (chain) => chain.toggleBlockquote() },
code: { type: 'code', command: (chain) => chain.toggleCode() },
codeBlock: { type: 'codeBlock', command: (chain) => chain.toggleCodeBlock() },
div: { type: 'div', command: (chain) => chain.toggleNode('div', 'paragraph') },
em: { type: 'italic', command: (chain) => chain.setItalic() },
ol: { type: 'orderedList', command: (chain) => chain.toggleOrderedList() },
strong: { type: 'bold', command: (chain) => chain.setBold() },
s: { type: 'strike', command: (chain) => chain.setStrike() },
span: { type: 'span', command: (chain) => chain.toggleMark('span') },
u: { type: 'underline', command: (chain) => chain.setUnderline() },
ul: { type: 'bulletList', command: (chain) => chain.toggleBulletList() },
};

override execute(editor?: Editor, item?: MetaTiptapToolbarStyleMenuItem) {
if (!editor || !item?.data) return;
const { tag, id, class: className } = item.data;
const focus = editor.chain().focus();
const ext = tag ? this.#commands[tag] : null;
(ext?.command?.(focus) ?? focus).setId(id, ext?.type).setClassName(className, ext?.type).run();
}

Check warning on line 33 in src/Umbraco.Web.UI.Client/src/packages/tiptap/components/toolbar/style-menu.tiptap-toolbar-api.ts

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v15/dev)

❌ New issue: Complex Method

UmbTiptapToolbarStyleMenuApi.execute has a cyclomatic complexity of 9, threshold = 9. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export class UmbTiptapToolbarColorPickerButtonElement extends UmbTiptapToolbarBu
<uui-popover-container id="color-picker-popover" placement="bottom-end">
<umb-popover-layout>
<uui-scroll-container>
<uui-color-picker inline @change=${this.#onChange}></uui-color-picker>
<uui-color-picker inline label=${label} @change=${this.#onChange}></uui-color-picker>
</uui-scroll-container>
</umb-popover-layout>
</uui-popover-container>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ export class UmbTiptapToolbarMenuElement extends UmbLitElement {
}

async #setMenu() {
if (!this.#manifest?.meta.items) return;
this.#menu = await this.#getMenuItems(this.#manifest.meta.items);
const items = this.#manifest?.items ?? this.#manifest?.meta.items;
if (!items) return;
this.#menu = await this.#getMenuItems(items);
}

async #getMenuItems(items: Array<MetaTiptapToolbarMenuItem>): Promise<Array<UmbCascadingMenuItem>> {
Expand Down Expand Up @@ -92,10 +93,10 @@ export class UmbTiptapToolbarMenuElement extends UmbLitElement {
}

return {
icon: item.icon,
icon: item.appearance?.icon ?? item.icon,
items,
label: item.label,
style: item.style,
style: item.appearance?.style ?? item.style,
separatorAfter: item.separatorAfter,
element,
execute: () => this.api?.execute(this.editor, item),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ const kinds: Array<UmbExtensionManifestKind> = [
element: () => import('../components/toolbar/tiptap-toolbar-menu.element.js'),
},
},
{
type: 'kind',
alias: 'Umb.Kind.TiptapToolbar.StyleMenu',
matchKind: 'styleMenu',
matchType: 'tiptapToolbarExtension',
manifest: {
api: () => import('../components/toolbar/style-menu.tiptap-toolbar-api.js'),
element: () => import('../components/toolbar/tiptap-toolbar-menu.element.js'),
},
},
];

const coreExtensions: Array<ManifestTiptapExtension> = [
Expand Down Expand Up @@ -581,17 +591,17 @@ const toolbarExtensions: Array<UmbExtensionManifest> = [
alias: 'Umb.Tiptap.Toolbar.FontFamily',
name: 'Font Family Tiptap Extension',
api: () => import('./toolbar/font-family.tiptap-toolbar-api.js'),
items: [
{ label: 'Sans serif', appearance: { style: 'font-family: sans-serif;' }, data: 'sans-serif' },
{ label: 'Serif', appearance: { style: 'font-family: serif;' }, data: 'serif' },
{ label: 'Monospace', appearance: { style: 'font-family: monospace;' }, data: 'monospace' },
{ label: 'Cursive', appearance: { style: 'font-family: cursive;' }, data: 'cursive' },
{ label: 'Fantasy', appearance: { style: 'font-family: fantasy;' }, data: 'fantasy' },
],
meta: {
alias: 'umbFontFamily',
icon: 'icon-ruler-alt',
label: 'Font family',
items: [
{ label: 'Sans serif', style: 'font-family: sans-serif;', data: 'sans-serif' },
{ label: 'Serif', style: 'font-family: serif;', data: 'serif' },
{ label: 'Monospace', style: 'font-family: monospace;', data: 'monospace' },
{ label: 'Cursive', style: 'font-family: cursive;', data: 'cursive' },
{ label: 'Fantasy', style: 'font-family: fantasy;', data: 'fantasy' },
],
},
},
{
Expand All @@ -600,21 +610,21 @@ const toolbarExtensions: Array<UmbExtensionManifest> = [
alias: 'Umb.Tiptap.Toolbar.FontSize',
name: 'Font Size Tiptap Extension',
api: () => import('./toolbar/font-size.tiptap-toolbar-api.js'),
items: [
{ label: '8pt', data: '8pt;' },
{ label: '10pt', data: '10pt;' },
{ label: '12pt', data: '12pt;' },
{ label: '14pt', data: '14pt;' },
{ label: '16pt', data: '16pt;' },
{ label: '18pt', data: '18pt;' },
{ label: '24pt', data: '24pt;' },
{ label: '26pt', data: '26pt;' },
{ label: '48pt', data: '48pt;' },
],
meta: {
alias: 'umbFontSize',
icon: 'icon-ruler',
label: 'Font size',
items: [
{ label: '8pt', data: '8pt;' },
{ label: '10pt', data: '10pt;' },
{ label: '12pt', data: '12pt;' },
{ label: '14pt', data: '14pt;' },
{ label: '16pt', data: '16pt;' },
{ label: '18pt', data: '18pt;' },
{ label: '24pt', data: '24pt;' },
{ label: '26pt', data: '26pt;' },
{ label: '48pt', data: '48pt;' },
],
},
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,54 @@
export const manifests: Array<UmbExtensionManifest> = [
{
type: 'tiptapToolbarExtension',
kind: 'menu',
kind: 'styleMenu',
alias: 'Umb.Tiptap.Toolbar.StyleSelect',
name: 'Style Select Tiptap Extension',
api: () => import('./style-select.tiptap-toolbar-api.js'),
items: [
{
label: 'Headers',
items: [
{
label: 'Page header',
appearance: { icon: 'icon-heading-2', style: 'font-size: x-large;font-weight: bold;' },
data: { tag: 'h2' },
},
{
label: 'Section header',
appearance: { icon: 'icon-heading-3', style: 'font-size: large;font-weight: bold;' },
data: { tag: 'h3' },
},
{
label: 'Paragraph header',
appearance: { icon: 'icon-heading-4', style: 'font-weight: bold;' },
data: { tag: 'h4' },
},
],
},
{
label: 'Blocks',
items: [{ label: 'Paragraph', appearance: { icon: 'icon-paragraph' }, data: { tag: 'p' } }],
},
{
label: 'Containers',
items: [
{
label: 'Block quote',
appearance: { icon: 'icon-blockquote', style: 'font-style: italic;' },
data: { tag: 'blockquote' },
},
{
label: 'Code block',
appearance: { icon: 'icon-code', style: 'font-family: monospace;' },
data: { tag: 'codeBlock' },
},
],
},
],
meta: {
alias: 'umbStyleSelect',
icon: 'icon-palette',
label: 'Style Select',
items: [
{
label: 'Headers',
items: [
{ label: 'Page header', data: 'h2', style: 'font-size: x-large;font-weight: bold;' },
{ label: 'Section header', data: 'h3', style: 'font-size: large;font-weight: bold;' },
{ label: 'Paragraph header', data: 'h4', style: 'font-weight: bold;' },
],
},
{
label: 'Blocks',
items: [{ label: 'Paragraph', data: 'p' }],
},
{
label: 'Containers',
items: [
{ label: 'Quote', data: 'blockquote', style: 'font-style: italic;' },
{ label: 'Code', data: 'codeBlock', style: 'font-family: monospace;' },
],
},
],
},
},
];
Loading
Loading