Skip to content

Commit

Permalink
Bugfix: TinyMCE anchor links without base URL (#2462)
Browse files Browse the repository at this point in the history
* Fixes TinyMCE anchor links without a base URL

it was previously prefixing the `href` with the current backoffice page URL.

* Code tidy-up + remove unused code
  • Loading branch information
leekelleher authored Oct 16, 2024
1 parent 722c508 commit ab7c35d
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 65 deletions.
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import type { UmbLinkPickerModalValue } from '../link-picker-modal/link-picker-modal.token.js';
import { UMB_LINK_PICKER_MODAL } from '../link-picker-modal/link-picker-modal.token.js';
import type { UmbLinkPickerLink } from '../link-picker-modal/types.js';
import { type TinyMcePluginArguments, UmbTinyMcePluginBase } from '@umbraco-cms/backoffice/tiny-mce';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import type { UmbLinkPickerLink, UmbLinkPickerLinkType } from '../link-picker-modal/types.js';
import type { UmbLinkPickerModalValue } from '../link-picker-modal/link-picker-modal.token.js';
import { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api';
import { UmbTinyMcePluginBase } from '@umbraco-cms/backoffice/tiny-mce';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import type { TinyMcePluginArguments } from '@umbraco-cms/backoffice/tiny-mce';

type AnchorElementAttributes = {
'data-anchor'?: string | null;
href?: string | null;
title?: string | null;
target?: string | null;
'data-anchor'?: string | null;
rel?: string | null;
text?: string;
title?: string | null;
type?: UmbLinkPickerLinkType;
rel?: string | null;
};

export default class UmbTinyMceMultiUrlPickerPlugin extends UmbTinyMcePluginBase {
Expand All @@ -21,16 +23,8 @@ export default class UmbTinyMceMultiUrlPickerPlugin extends UmbTinyMcePluginBase

constructor(args: TinyMcePluginArguments) {
super(args);
const localize = new UmbLocalizationController(args.host);

// const editorEventSetupCallback = (buttonApi: { setEnabled: (state: boolean) => void }) => {
// const editorEventCallback = (eventApi: { element: Element}) => {
// buttonApi.setEnabled(eventApi.element.nodeName.toLowerCase() === 'a' && eventApi.element.hasAttribute('href'));
// };

// editor.on('NodeChange', editorEventCallback);
// return () => editor.off('NodeChange', editorEventCallback);
// };
const localize = new UmbLocalizationController(args.host);

this.editor.ui.registry.addToggleButton('link', {
icon: 'link',
Expand All @@ -57,39 +51,27 @@ export default class UmbTinyMceMultiUrlPickerPlugin extends UmbTinyMcePluginBase
const selectedElm = this.editor.selection.getNode();
this.#anchorElement = this.editor.dom.getParent(selectedElm, 'a[href]') as HTMLAnchorElement;

const data: AnchorElementAttributes = {
text: this.#anchorElement
? this.#anchorElement.innerText || (this.#anchorElement.textContent ?? '')
: this.editor.selection.getContent({ format: 'text' }),
href: this.#anchorElement?.getAttribute('href') ?? '',
target: this.#anchorElement?.target ?? '',
rel: this.#anchorElement?.rel ?? '',
};

if (selectedElm.nodeName === 'IMG') {
data.text = ' ';
}

if (!this.#anchorElement) {
this.#openLinkPicker({ name: this.editor.selection.getContent() });
this.#openLinkPicker({ name: this.editor.selection.getContent({ format: 'text' }) });
return;
}

//if we already have a link selected, we want to pass that data over to the dialog
let url = this.#anchorElement.getAttribute('href') ?? this.#anchorElement.href ?? '';

const queryString = this.#anchorElement.getAttribute('data-anchor') ?? '';
if (queryString && url.endsWith(queryString)) {
url = url.substring(0, url.indexOf(queryString));
}

const currentTarget: UmbLinkPickerLink = {
name: this.#anchorElement.title || this.#anchorElement.textContent,
target: this.#anchorElement.target,
queryString: `${this.#anchorElement.search}${this.#anchorElement.hash}`,
queryString: queryString,
type: (this.#anchorElement.type as UmbLinkPickerLinkType) ?? 'external',
unique: url.includes('localLink:') ? url.substring(url.indexOf(':') + 1, url.indexOf('}')) : null,
url: url,
};

if (this.#anchorElement.href.includes('localLink:')) {
const href = this.#anchorElement.getAttribute('href')!;
currentTarget.unique = href.substring(href.indexOf(':') + 1, href.indexOf('}'));
} else if (this.#anchorElement.host.length) {
currentTarget.url = this.#anchorElement.protocol ? this.#anchorElement.protocol + '//' : undefined;
currentTarget.url += this.#anchorElement.host + this.#anchorElement.pathname;
}

this.#openLinkPicker(currentTarget);
}

Expand All @@ -112,35 +94,32 @@ export default class UmbTinyMceMultiUrlPickerPlugin extends UmbTinyMcePluginBase

// TODO: This is a workaround for the issue where the link picker modal is returning a frozen object, and we need to extract the link into smaller parts to avoid the frozen object issue.
this.#linkPickerData = { link: { ...linkPickerData.link } };

this.#updateLink();
}

//Create a json obj used to create the attributes for the tag
// TODO => where has rel gone?
#createElemAttributes() {
// Attribute 'name' because of linkPickerData. It should be 'title' .
const { name, ...linkPickerData } = this.#linkPickerData!.link;
const a: AnchorElementAttributes = Object.assign({}, linkPickerData);

// always need to map back to href for tinymce to render correctly
// do this first as checking querystring below may modify the href property
if (this.#linkPickerData?.link.url) {
a.href = this.#linkPickerData.link.url;
}
const link = this.#linkPickerData!.link;

const anchor: AnchorElementAttributes = {
href: link.url ?? '',
title: link.name ?? link.url ?? '',
target: link.target,
type: link.type ?? 'external',
rel: link.target === '_blank' ? 'noopener' : null,
};

if (this.#linkPickerData?.link.name) {
a.title = name;
}
if (link.queryString) {
anchor['data-anchor'] = link.queryString;

if (
this.#linkPickerData?.link.queryString?.startsWith('#') ||
this.#linkPickerData?.link.queryString?.startsWith('?')
) {
a['data-anchor'] = this.#linkPickerData?.link.queryString;
a.href += this.#linkPickerData?.link.queryString;
if (link.queryString.startsWith('?')) {
anchor.href += !anchor.href ? '/' + link.queryString : link.queryString;
} else if (link.queryString.startsWith('#')) {
anchor.href += link.queryString;
}
}

return a;
return anchor;
}

#insertLink() {
Expand Down
10 changes: 6 additions & 4 deletions src/packages/tiny-mce/plugins/tiny-mce-code-editor.plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@ export default class UmbTinyMceCodeEditorPlugin extends UmbTinyMcePluginBase {
},
});

if (!modal) return;
const value = await modal.onSubmit().catch(() => undefined);
if (!value) {
return;
}

const { content } = await modal.onSubmit();
if (!content) {
if (!value.content) {
this.editor.resetContent();
} else {
this.editor.setContent(content.toString());
this.editor.setContent(value.content.toString());
}

this.editor.dispatch('Change');
Expand Down

0 comments on commit ab7c35d

Please sign in to comment.