Skip to content
2 changes: 2 additions & 0 deletions src/Umbraco.Web.UI.Client/src/assets/lang/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2737,6 +2737,8 @@ export default {
config_overlaySize_description: 'Select the width of the overlay (link picker).',
},
tiptap: {
anchor: 'Anchor',
anchor_input: 'Enter an anchor ID',
config_dimensions_description: 'Set the maximum width and height of the editor. This excludes the toolbar height.',
config_extensions: 'Capabilities',
config_toolbar: 'Toolbar',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Node, mergeAttributes } from '@tiptap/core';

export const Anchor = Node.create({
name: 'anchor',

atom: true,
draggable: true,
inline: true,
group: 'inline',
marks: '',
selectable: true,

addAttributes() {
return {
id: {},
};
},

addNodeView() {
return ({ HTMLAttributes }) => {
const dom = document.createElement('span');
dom.setAttribute('data-umb-anchor', '');
dom.setAttribute('title', HTMLAttributes.id);

const icon = document.createElement('uui-icon');
icon.setAttribute('name', 'icon-anchor');

dom.appendChild(icon);

return { dom };
};
},

addOptions() {
return {
HTMLAttributes: {
id: 'id',
},
};
},

parseHTML() {
return [
{
tag: 'a[id]',
getAttrs: (element) => (element.innerHTML === '' ? {} : false),
},
];
},

renderHTML({ HTMLAttributes }) {
return ['a', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
},
});
1 change: 1 addition & 0 deletions src/Umbraco.Web.UI.Client/src/external/tiptap/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export { TextAlign } from '@tiptap/extension-text-align';
export { Underline } from '@tiptap/extension-underline';

// CUSTOM EXTENSIONS
export * from './extensions/tiptap-anchor.extension.js';
export * from './extensions/tiptap-div.extension.js';
export * from './extensions/tiptap-figcaption.extension.js';
export * from './extensions/tiptap-figure.extension.js';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1075,7 +1075,7 @@ export const data: Array<UmbMockDataTypeModel> = [
'Umb.Tiptap.Toolbar.Blockquote',
'Umb.Tiptap.Toolbar.HorizontalRule',
],
['Umb.Tiptap.Toolbar.Link', 'Umb.Tiptap.Toolbar.Unlink'],
['Umb.Tiptap.Toolbar.Anchor', 'Umb.Tiptap.Toolbar.Link', 'Umb.Tiptap.Toolbar.Unlink'],
['Umb.Tiptap.Toolbar.Table', 'Umb.Tiptap.Toolbar.MediaPicker', 'Umb.Tiptap.Toolbar.EmbeddedMedia'],
],
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -911,10 +911,10 @@ export const data: Array<UmbMockDocumentModel> = [
value: {
blocks: undefined,
markup: `
<p><a id="anchor"></a> Here is a link for <a href="https://gist.github.com/leekelleher/9490718" target="_blank">all HTML tags</a>.</p>
<p>
<span id="foo">Some</span> value for the RTE with an <a href="https://google.com">external link</a> and an <a type="document" href="/{localLink:c05da24d-7740-447b-9cdc-bd8ce2172e38}">internal link</a>.
</p>
<p><a href="https://gist.github.com/leekelleher/9490718" target="_blank">All HTML tags</a></p>
<div data-foo-bar="123">
<span>This is a plain old span tag.</span>
<span style="color:red;">Hello <span style="color:blue;">world</span>.</span>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import type { UmbTiptapAnchorModalData, UmbTiptapAnchorModalValue } from './anchor-modal.token.js';
import { css, customElement, html } from '@umbraco-cms/backoffice/external/lit';
import { umbFocus } from '@umbraco-cms/backoffice/lit-element';
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';

@customElement('umb-tiptap-anchor-modal')
export class UmbTiptapAnchorModalElement extends UmbModalBaseElement<
UmbTiptapAnchorModalData,
UmbTiptapAnchorModalValue
> {
async #onSubmit(event: SubmitEvent) {
event.preventDefault();

const form = event.target as HTMLFormElement;
if (!form) return;

const isValid = form.checkValidity();
if (!isValid) return;

const formData = new FormData(form);
const name = formData.get('name') as string;

this.value = name;
this._submitModal();
}

override render() {
const label = this.localize.term('tiptap_anchor_input');
return html`
<uui-dialog-layout>
<uui-form>
<form id="form" @submit=${this.#onSubmit}>
<uui-form-layout-item>
<uui-label for="name" slot="label" required>${label}</uui-label>
<uui-input
type="text"
required
id="name"
name="name"
label=${label}
.value=${this.data?.id || ''}
${umbFocus()}></uui-input>
</uui-form-layout-item>
</form>
</uui-form>
<uui-button
slot="actions"
label=${this.localize.term('buttons_confirmActionCancel')}
@click=${this._rejectModal}></uui-button>
<uui-button
type="submit"
slot="actions"
form="form"
color="positive"
look="primary"
label=${this.localize.term('general_submit')}></uui-button>
</uui-dialog-layout>
`;
}

static override styles = [
css`
:host {
--umb-body-layout-color-background: var(--uui-color-surface);
}

uui-dialog-layout {
width: var(--uui-size-100);
}

uui-input {
width: 100%;
}
`,
];
}

export { UmbTiptapAnchorModalElement as element };

declare global {
interface HTMLElementTagNameMap {
'umb-tiptap-anchor-modal': UmbTiptapAnchorModalElement;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { UMB_TIPTAP_ANCHOR_MODAL_ALIAS } from './constants.js';
import { UmbModalToken } from '@umbraco-cms/backoffice/modal';

export type UmbTiptapAnchorModalData = {
id?: string;
};

export type UmbTiptapAnchorModalValue = string;

export const UMB_TIPTAP_ANCHOR_MODAL = new UmbModalToken<UmbTiptapAnchorModalData, UmbTiptapAnchorModalValue>(
UMB_TIPTAP_ANCHOR_MODAL_ALIAS,
{
modal: { type: 'dialog' },
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const UMB_TIPTAP_ANCHOR_MODAL_ALIAS = 'Umb.Modal.Tiptap.Anchor';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './anchor-modal.token.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { UMB_TIPTAP_ANCHOR_MODAL_ALIAS } from './constants.js';
import type { ManifestModal } from '@umbraco-cms/backoffice/modal';

export const manifests: Array<ManifestModal> = [
{
type: 'modal',
alias: UMB_TIPTAP_ANCHOR_MODAL_ALIAS,
name: 'Tiptap Anchor Modal',
element: () => import('./anchor-modal.element.js'),
},
];
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './input-tiptap/index.js';
export * from './anchor-modal/index.js';
export * from './cascading-menu-popover/cascading-menu-popover.element.js';
export * from './character-map/index.js';
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { manifests as anchorModal } from './anchor-modal/manifests.js';
import { manifests as characterMap } from './character-map/manifests.js';

export const manifests: Array<UmbExtensionManifest> = [
...anchorModal,
...characterMap,
];
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,2 +1,3 @@
export * from './components/anchor-modal/constants.js';
export * from './components/character-map/constants.js';
export * from './property-editors/tiptap/constants.js';
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import { UmbTiptapExtensionApiBase } from '../base.js';
import { css } from '@umbraco-cms/backoffice/external/lit';
import { Div, HtmlGlobalAttributes, Span, StarterKit, TrailingNode } from '@umbraco-cms/backoffice/external/tiptap';
import {
Anchor,
Div,
HtmlGlobalAttributes,
Span,
StarterKit,
TrailingNode,
} from '@umbraco-cms/backoffice/external/tiptap';

export class UmbTiptapRichTextEssentialsExtensionApi extends UmbTiptapExtensionApiBase {
getTiptapExtensions = () => [
StarterKit,
Anchor,
Div,
Span,
HtmlGlobalAttributes.configure({
Expand Down Expand Up @@ -76,6 +84,19 @@ export class UmbTiptapRichTextEssentialsExtensionApi extends UmbTiptapExtensionA
padding: 0;
}
}

span[data-umb-anchor] {
&.ProseMirror-selectednode {
border-radius: var(--uui-border-radius);
outline: 2px solid var(--uui-color-selected);
}

uui-icon {
height: 1rem;
width: 1rem;
vertical-align: text-bottom;
}
}
`;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,18 @@ const toolbarExtensions: Array<UmbExtensionManifest> = [
label: 'Ordered List',
},
},
{
type: 'tiptapToolbarExtension',
kind: 'button',
alias: 'Umb.Tiptap.Toolbar.Anchor',
name: 'Anchor Tiptap Extension',
api: () => import('./toolbar/anchor.tiptap-toolbar-api.js'),
meta: {
alias: 'anchor',
icon: 'icon-anchor',
label: '#tiptap_anchor',
},
},
{
type: 'tiptapToolbarExtension',
kind: 'button',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { UmbTiptapToolbarElementApiBase } from '../base.js';
import { UMB_TIPTAP_ANCHOR_MODAL } from '../../components/anchor-modal/index.js';
import { Anchor } from '@umbraco-cms/backoffice/external/tiptap';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import type { Editor } from '@umbraco-cms/backoffice/external/tiptap';

export default class UmbTiptapToolbarAnchorExtensionApi extends UmbTiptapToolbarElementApiBase {
override async execute(editor?: Editor) {
const attrs = editor?.getAttributes(Anchor.name);
if (!attrs) return;

const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const modal = modalManager.open(this, UMB_TIPTAP_ANCHOR_MODAL, { data: { id: attrs?.id } });
if (!modal) return;

const result = await modal.onSubmit().catch(() => undefined);
if (!result) return;

editor
?.chain()
.insertContent({ type: Anchor.name, attrs: { id: result } })
.run();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export class UmbTiptapToolbarConfigurationContext extends UmbContextBase<UmbTipt
numlist: 'Umb.Tiptap.Toolbar.OrderedList',
outdent: null,
indent: null,
anchor: null,
anchor: 'Umb.Tiptap.Toolbar.Anchor',
table: 'Umb.Tiptap.Toolbar.Table',
hr: 'Umb.Tiptap.Toolbar.HorizontalRule',
subscript: 'Umb.Tiptap.Toolbar.Subscript',
Expand Down
Loading